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 697ce4bbae..fee620c6da 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 @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "alarm_widgets", "title": "Alarm widgets", - "image": null + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAOPElEQVR42u2deVsTVx+G/Xb9ALVeffuHtmql2kVrVWytWltr64KoqIgbolRFRMQNtCwioiLIqii7grIoShARwnbee3Js3pgibzYQzPNcXLmGyUwmc+ae35kE7jOzjDHDw8MvFCVCGRoaAqpZlqqxsTGjKGEHkMAJqGaJKmUy2HLAUlsokY0DVk9PjxpCiWyASmApAksRWIrAEliKwFI+DLD4pst3gbKyss7OTjWcEi5Yubm5X3zxxejoqP01ISGhtLRUDaeEC9ZPP/30xx9/UKj8wBoYGLh9+3Z5ebllrrW1taOj49atW263u6mpqaWl5ebNm/39/c+ePbt+/bq3yD1//vzatWsNDQ1q+qgG6/Hjx+vWrauvr9+8ebMvWPyVccWKFZmZmXv37k1MTGT+rl27li1bduLECfD67LPPjh07lpyc/N133+3YsSMrK2vhwoUvX77s7u5mzvnz59evX3/lyhW1fvSCBR95eXlMAERvb68XLMoSzLW3t9+9e3fx4sUWrJKSEiYAKzY21q4+b948ChsT27Ztq6mpoZKtXLmSFYHs0aNHav0oBYs+jquruLi4PXv2fPPNN1QaL1ivX7/++eefjx49mpaWFhMTY8G6c+eOBWv16tX2FebPn287SupWZWUlE3SdW7duXbVqFVVQrR+lYAEQPWCnJw8ePKCn84IFJfBh+8pFixYFCFZtbS0XXvxaUVFBDVPrRylYf/75p/ea3V7FNzY2WrC4Kl++fDld3u+///7pp59CTyBg0ZlydbVp0yYY9X1lJRo/FU4Qe/0UbAYHB71fXigCS1EEliKwFIGlKAJLEViKwFIUgaUILEVgKYrAUgSWIrAURWApAksRWIoisBSBpXz4YO3evXvleEHameJ3WVBQoEP14YAFQ0HNn7xM/RZHRkaCWp4RLt7j1qMOLLyd77///qOPPlq6dOmpU6dmBFjV1dWYkrzt1NTUwNc6c+YMCi7jXIQv4m7YsIH9xRB++vQptrDfC+JjFhUVqWI5WbBgAboYLYLyigN98ODBnJwchDCU1ydPnvT19WEvopG5XC6/FfGt/bZoBxCf1Ozfv/+vv/7CI2JkAEb/PXLkCO8TrRIpnIYCHexIhp9g5qVLl3DdEHoZZODcuXNYcXPnzv31119Pnz798OHDGzduWDU8qKAwffzxx69evUIc5xVQyeGMRkPDZAKLk+3SnnV1dbxJe7lCecO941lWiUawkKcZwYEDk5KSQnNw2VRYWMhR2bdvH3MOHTrEkfNdC4eR1gQ77xZpbpZn/qS2CEeRQVC+/vpryi2jnvz444/FxcWIkOCSnZ3NBOOaoFJyFBmi4t69exRj9o63SqnjbGEt9vHw4cPIlZw2IbyBq1evfvXVV7wUkO3cuZMXbGtr4zWTkpIOHDgA0zxCGJqn8QxiANw4w7wf63VGHVhM/Pbbb19++SWNAliczRwJ5lCrOOE44/99wgEfvjWaK1u0VE3BUCKXL19mKGnomTNnDiQx9gnvLT8/n5n0j2vXrmX8nNmzZzOTksZ7pkQZz+AUXrA4hehM6ctC2DrbZR+plCDFJixYbJ2CRO2kNFqwQJZhCsw/o2Pgl9OMdAVRChb7D1i2gHOQOC/Bi2PDYaDCc2D+vS6tDE9scWqoMp6BBRiogi1u374dpildHFR7gYj8zTFmgpJGwaAHZzwmX7BYzDYOJ0xo15T0a7/88gutQXVvbm5OT0/nBRkcyl72bdmyhSsKOlwuvBhWg01/8sknjOFD1WQV2zlG3adCPjfR2XGRa8ECKe8nKS5oJvgcRN1ii1M87JHvxdy4n/je9TGQFbno5g1T2CKydTvtuzk7x6/RpuDqM2Jg8X3VuN9jMSZWCFuiaFHbbVtQpWj9wNdl/KMZ9EmboZ0Y9UTfY+mbd0VgKQJLEVgCSxFYisBSBJaiCCxlRoDFXzF143UlsgEqVSxFXaEisBSBJbAUgaUILEVgKYrAUgSWIrD8M30Ue+WDAmv6KPbTKSNmbNLviee1JDAD8N6CXZ07vX/gYCFB4KOiK/GI/BmpN42CPIlN0pRsbi14M125xtRufuvZzlzTM7nCMboi5hl3IcXIOHv2bAh+M45hVFQsa95ZICAM/47GQn1GRqXhsFXxQnkP9LDcvhUP2C6JnFnjCQtwO9aMjAy6Zqxz5EScO3xo5hiPeIi7zKuxOiIQE2zCaoyhg1U027xsMO4eU/wfB6zhl6Z2m6neYPrbTFehcVWb/sfm3iZzP84M95vWDHNvc6RoQ1XFSbRF6+LFixYs7EUkVSsqImTTSjQCFj86K24c06iqqNKsgi3NNI5hFIGFvLtx40YGQUD1ZKwLWoQ/gNtGRNFk2lpl69atwyjn9r4suWbNmlxPcOiQNquqqhDevWekfQQjjE2W5JDQprwswOGZhQVWfaKpTzCt6c4EYIERhaphv2lIMi1HTcffpiLWmfnkvGlJNTUbTddVp4uM0PFAp/b+asFir2kNWg+J3LYDbcgjd3oHph9++IGhHDC2UTVh0SqvUQQWDUR5t2cYYFF7qDSccxYRwKLYGI9DTJMhGbMkjQhVLE8FYj53leaW0l6kLJQWLDuHtRhuhBU5m8MC60mOKVvm/DwvdcDqLDA1Gxyk6ve+Aaskxgy5nPnQBli9dRG8ukK89qtYGNgFnrS3t9t2AB1aiUFWaBkMafssbcUwE1HXFdJSmOmMlnHhwoVxwUJsT/IE69cuSctasBgNgTOSzpFlWJ5SzzlKj0CRYzwML1gM0QG7LBbWWEKA1Z5jWk+ZugTzvNwBq7vEIenOcqfLs2B15pmKFQ55/a2RBctWKXYNm55RGyxY6OPsFDuL8m/b4dtvv7VgsTy/UqqxzBlAhaEl6DSp31H3qfBdZrqtWL7P+i3pa5SPeGLG88ontvXDuPbxvJnhAefSqqvozcfDSQv77nc/9omHI/A+y/XANBm0beoU+4lDSecyYrp/1eB+YVrPvIFMCRksRRFYisBSBJaiCCxFYCkCS1EEljKdwJJir0ixV9QVKgJLDaEILEVgKQJLUQSWIrAUgeUXmdDKpIAV1Sb02/9y7vzq+7/kE9zDDQs5HKvRE5nQ/ycz0oTu7jbcHHX1ahTH/8HETTrz8kxdncGvev3aJCW9c3V8obqwpB2Z0IFmhpnQKSkmJ8eZ4IalbW0mPt6sXeuAVVho0GUXLjS4ZYcPO8whleAc44Bw7+dNm5wf7mAdHlgyoYMGa8aY0Bs3Gt8iMWcOHZLJzXWAKyhwkHrxwixdyj3BTUKC89TZs6a21sGO3fz77zDBkgkdNFgzxoQGHTAimZlOKZo715n+N1gXL5rUVKduUdWYmZhotmyh0oYJlkzooMGaMSY0RWjJEqeP4wdufMGi15s3z+EGsFwu51KMprhwgf7JmV60yNBbhX2NJRM6lMwYE3pwcPz5brff/vhPRCIyoSOWmWFCKxEBS1EEliKwFIGlKAJLEViKwFIUgaVMJ7BkQisyoRV1hYrAUkMoAksRWIrAUhSBpQgsRWD5RSa0MilgTXMTmv/v9k5b99d3zlRmZHTkvex1VIMVlONmzacA17KOio21dOycMD3pQ3eTXwz0lHXeyWzI4tftZfFjZmxiqi42Z0f2qKAhlZeX+81EbRoYGEAe7OrqElhvIYJKiquE1YTShMrMI9OoWjyOC5Z1ne0jRhRd8PHjx9GbUDpxUex8BClec/HixXbJoqIiPGnMuxyPzWzt4aCS+zCv+llNen3G/qoDPYM9KfdSe929p+szTj5I63W/LOkoOV13pvb5/cyGc8fvnyxoLRweGb788Epnf+e5pgssXNlVNWrGeDajPjO7JSeEQ8If2rgIsdoqAzewv7jg9fX1S5YswZxDm6PF7CnE/iLG4cnRArhfUQpWbW0terilh7EuUlJSsOFoEY496rNdBj72eTJ//nzztvGMIc0j6qbb7T5y5Ah9H/NdLldcXBzz8en8lme7HIBUnNIg0+hqutScDSKZjVnFT24WtBV29HdUP7sLK9cfF4NLRVely+3aVbFn1IxuLY0bHB3cU7mvsacp7cGpvqE+pmu677Lu4Mjg9rIdIRySkydPMuYFxiXmUnJyMpIqEjnnHnhhYmI/AxnWIY4hDcUOcgrBWXp6epSCxamWn5/PBDIqFwrx8fGHPKG93tUVWtfZFxc0c1uHaFzmdHZ2ckL7LWMfaXE8fY5KsC3iHnFvK4271JINTFtKtz3qbb3RfutMQxb9XeHja4D1sPdRn7vvQNVBFo6/s9MLVlbjebrFXeV76EZzH+XbZ0M4JCjzEMObZ6AA6hZ4UZ4pSL5gARMNiLXLApQxNOjm5uboAouOyRYhKjxIQRJnJPMxnlGcORHp494FFhix4ueff/4usJhGfabysRXvMrGxsdQzLkToO0JrFKpR3Yv6V0Ov1havh5XKp5V7q/YlViXR5QUCFs/urkg8cT8thIqFzG3bh3OPRgCdzZ6wv3R8aZ4wTQ8YExPDYkzTqrQSlxZR/akwWE93OADD2O81WYU5IBvW0CBvZ3RsNPCF6QRvdty63VF6rPZ4BD5y/qN9O9XUz8O2722SboM9eWBNpQkd2cAWVL3H5qbglbTfHhgZ0PdYiiKwFIGlCCxFEViKwFIElqIILGWagyUTWpEJragrVASWGkIRWIrAUgSWoggsRWApAssvMqGVSQFLJnSAkQn9HsCSCR1CZEIHB5ZM6EAiEzo4sGRCBxiZ0MGBJRM6wMiEDigyoYOKTOhQP0DJhA6+xWRCT5fIhJ7WYCmKwFIEliKwFEVgKdMMLL5hUkMokQ1QOWDNlL+ZKzMi4OSANTQ0JLaUyFLFl4izjOcbxe7ubv709lRRwggIAZL9avq/0p2LbK71A+cAAAAASUVORK5CYII=", + "description": "Visualization of alarms for devices, assets and other entities." }, "widgetTypes": [ { "alias": "alarms_table", "name": "Alarms table", + "image": "data:image/png;base64,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", + "description": "Displays alarms based on defined time window and other filters.", "descriptor": { "type": "alarm", "sizeX": 10.5, @@ -16,8 +19,8 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableFilter\": {\n \"title\": \"Enable alarm filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableFilter\",\n \"enableStickyAction\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableFilter\": {\n \"title\": \"Enable alarm filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableFilter\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"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}],\"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}" } } diff --git a/application/src/main/data/json/system/widget_bundles/analogue_gauges.json b/application/src/main/data/json/system/widget_bundles/analogue_gauges.json index 95845bad1b..de453f03a7 100644 --- a/application/src/main/data/json/system/widget_bundles/analogue_gauges.json +++ b/application/src/main/data/json/system/widget_bundles/analogue_gauges.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "analogue_gauges", "title": "Analogue gauges", - "image": null + "image": "data:image/png;base64,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", + "description": "Display temperature, humidity, speed, and other latest values on various analog gauge widgets." }, "widgetTypes": [ { "alias": "analogue_compass", - "name": "Analogue Compass", + "name": "Compass", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAh70lEQVR42u2d+b9V0//H799yJTKTIUPGMiXKGJlSxjJkqkwJCSGRhPDVTESKjImKkCZRKZkyD6mIhL7Pz3k9PuvztvY++5zb2WcP9+73D/ex7xn22eu9Xus9r/dq2FLQli3//PPPn3/+uXHjxvXr1//8888//PDDd9999/XXX69Zs+aLEn3++eeflYgLvcJbfICP8WG+whf5OjfhVgU/oYaWOey///4bHPzyyy/ff//9V1999VmsxA25LTfnJ/ihAljNH0y//vrrjz/+GDuSKuKMH+WnWxTImj+wUE8Ij2+++eazDBCPwcPwSAWw8o2nKoXT6tWr33rrraeffvr++++/4YYbLrjggtNOO+2YY45p3779brvttuuuu+64446NJeKCf3mRt/gAH+PDfGXkyJFTpkx5++23uVWVYqx5I6y5AQvb+bfffvv222+j5/WTTz557bXXhg8ffskllxx99NEON7UTt+rUqRO3vffee2fOnMkPRT8JHsCGDRuan8nffIC1efNmvDP8tXJT+Omnn7766qtDhgw5+eSTY0RSNO20006nnHIKPwqOeYByz8Zj8/AMoQBWhmjTpk34/BHC6cknn+zdu/dee+3VmCrtvffeffr0mTx5cjkxRiyDgTCcAljpG1I49uXk09SpU5lI7KGtw0GrVq34u8022+y+++5cHFMiLviXF90Hqqc2rVvfe9JJXfbZh0dCVz733HPlZBiqPO/wamh+kPrggw9GjBhx2GGHNWnW9913X9QWF+ecc45e6devH39bt2595ZVX6l+9wr/bbrut+wDUs2dPaT1uEvETvQ4++Jcbb3yzd2/3Ch4AWnLRokWhA2GA+bXu8wcsokGYIy4UbmnOnDnMeps2bapB0n777Sc0nHvuuVwce+yxEkhXXHHFdtttZ3GjCwes/v37e8i7/PLLJdK4CRfcUBjVTziacOaZAOvGzp29J+EOuJavv/56qHIkBpbHAFjOgEXmJNQ8nzZtGjayNFQ0nXDCCVJhAwYM0CsCyp577nneeedxcfbZZwsQ5YDlvc6HzzrrLC7OP//8tm3bWuTpJ/g5frR1q1ZfDBgAsA7cddfQB+Phu3XrNn369FDTHs+xAFa9nL7QIMKMGTO6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", + "description": "Displays latest value of the attribute or timeseries key on the compass. Expects value to be in range of 0 to 360.", "descriptor": { "type": "latest", "sizeX": 6, @@ -23,7 +26,9 @@ }, { "alias": "temperature_gauge_canvas_gauges", - "name": "Temperature gauge - Canvas Gauges", + "name": "Thermometer scale", + "image": "data:image/png;base64,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", + "description": "Preconfigured widget to display temperature. Allows to configure temperature range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 7, @@ -39,7 +44,9 @@ }, { "alias": "temperature_radial_gauge_canvas_gauges", - "name": "Temperature radial gauge - Canvas Gauges", + "name": "Temperature radial gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display temperature. Allows to configure temperature range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 6, @@ -55,7 +62,9 @@ }, { "alias": "speed_gauge_canvas_gauges", - "name": "Speed gauge - Canvas Gauges", + "name": "Speed gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display speed. Allows to configure speed range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 7, @@ -71,7 +80,9 @@ }, { "alias": "radial_gauge_canvas_gauges", - "name": "Radial gauge - Canvas Gauges", + "name": "Radial gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 6, 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 437d56678e..fc6d4ce590 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "cards", "title": "Cards", - "image": null + "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." }, "widgetTypes": [ { "alias": "attributes_card", "name": "Attributes card", + "image": "data:image/png;base64,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", + "description": "Displays one or more latest values of the entity. Supports multiple entities.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -24,6 +27,8 @@ { "alias": "html_card", "name": "HTML Card", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=", + "description": "Useful to inject custom HTML code. Designed to displays static information only.", "descriptor": { "type": "static", "sizeX": 7.5, @@ -40,6 +45,8 @@ { "alias": "timeseries_table", "name": "Timeseries table", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAT90lEQVR42u2dCVcUV7eG/WVmMCtZMSZGE/M5RY0TBjR6naIxEXFAwKgQhSAKIiozQREUFRUFh4giIoogoAwyygw9Av09xZGibAiXSHut5u69znKdOnWqu4an9t5VNu+e4nK5nE5nc3NzTU3NCzGxCRgIAZLD4QCqKVBVW1vb2dnZ19fnEhObgIEQIIETUE0BMRbkpIh5ysAJqKbgvsRXiXnWbwHVFEKjnAsxzxpQCVhiApaYgCUmYAlYYgKWmIAlJmCJiQlYYgKWmIAl9pbW0dHR2toq5+FtwDp79uz/vGk5OTmXLl369ddfvehod+/efe3aNdXnf0k5ioaGhn/7IXv37j1z5oxx5PDhw5s2baKza9eujIwMOnAGbe/oKI4dO+Z2LR4+fOitYD179oxLcurUqalTpwIZfbZqbGy8f/++F4E1d+7cxMRE1a+uruZYnj9//m8/pLi4uK6ublSwioqKXr58SeeXX34JDQ19R0fBDnD+w8LCPvnkk2uDxk3i3aHw6dOnXAz9MLKzswMCAugcOnTowIEDP/300/Lly69fvx4cHLxo0SIG1bT8/PyVK1ey6vz58yYEi3tj9erVanDDhg23b98uLy9ftWpVREQERxESEnL16tUVK1b4+voqChlRHuvRo0c/DtratWsVWDizc+fOxcTETJ8+/auvvkpISMCHJScns6qgoIBP4FdKnjoWwsWnn36qL7Iza9as+eGHH7j5WeSg1q1bt3//fnUhLly4sGzZMvazvr6etevXr2dw6dKlHK+6E3p7e7lqCxcu/O233169esUIk7k3Fi9e/Hb7PCGw4uPj2Tk6W7ZsgZvHjx8HBgZ+8MEHmZmZwMRMrhDXb9q0aVeuXLlz5w532Ft4CM+CBTH7Bs3f31+BxZ2gX6Gvv/764sWLOB5WZWVlQcPHH3/MZSgtLeW22blzJ3PoREdHc7q/+eYbbieuKCdBgQVkx48fb29vJzzt2bOHaBgXF8elZRWXzbNpgxGsrq4uUIZgdubLL7/k3uCgPvzwQ077jRs3OBa+mkPgGik/OnPmzB07djx58kRFUnVLQFJFRQWnRe3nF198weFAZH9///sEC89Mh5t7xowZai083bp1Cw/BoZ4aNC4bYfT9grV582a1M+Hh4WODpe7Ub7/9lvtEZTa4BB0sNmROS0uLMRQqsIyhEH/AncbdNWvWLE7OOwJLYaSOC44jIyONB0UHvOjgwBQ0gMVh0uECcbcPDAzAJaixOWkovlaBNZEdfodgscc3b94kHLCjp4YMr2a2UMhJHwOsOXPmKLCAaSRYOKexwVKD27dv5yusVus7Ais3N/ejjz7ST/Ldu3eNYH322WcKLPyrG1iEEVwyPunzzz8nq1Gbc8nMCBY7ZASrrKyMYybBbGpq4nqoW9xUYBE+6JARcz3wsiPBIm0aCRZ/LwCFdPh9N+HVDSxI2rZtm/rBd1paGp+mwug7AouzSj8lJYVLAxlubhiwWHQDi0yRySyq/JIOoZCnMaI/O+wGFtmhn5+fseNhsCorK7k2bW1tapFHayKLSiDUCSW6kySqtd9//716ZuRS0ScgEsgtFst7BAsmlPtRQYpj4fmOQEA+hFvlWHx8fPLy8kg+WKV+rs15V+eXDEY9qfCvno/Pnz9/wYIFQMOhMULmm5qaSoc7ithHSKLP6SIaMuLZY2E/yaz1Re4KUijCBc6Sb2RRX7tkyRIW1b1BNFRgkTiSI7KJynrZhFDIOJGU+MgI15GrqT4BmBR/ekdekL5/4/JwU3nweXDipodCefPurUa+Qlqt3poKWGIeMxL2/59/WidgiQlYYgKWmIAlYIkJWGIClpiAJWCJCVhiApaYgCUmJmCJCVhiApaYmIAl5hVgVXu5RUVVHzjg9e3wvtKSiJNPoyZDmyQea/t2fm/u9W376rZ2/4OTowlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgTRqwUGxGSNgo/fv333+js41yHALU6OK5zWcEpVSlO0j/yJEjSMuha60E44zTkGZEEe/BgwdqBLHhFUM2Tv24cYIVHIwQ3uvGfjGybp0rK4sDQe3OfTLij2hEsuqvv5ACNCNYvSnnbTfv6X37vWLVehLPuc3sPHDMmpNvybpqOrAQeUIEHBFLNLvUCMpPs2fPRjccNU4UBIHMbRNU3ZFMhSfXoNQ7iDCT4ubIGdrtdn0aSKHYiUKhLkxIsQLUPrsGrbu724NgpaRQkALpSq3Fx2sjRUWuigqtz/fExLwxmfHSUldsrAtpdMpZmA2szoPRA3bHQI9FLToelTlf1NnyCmg9p9KNM7uOJgzY7I6S8pHAvX+w+gcNkWAdrJKSEiQ9VT8pKSkqKqqnpwfCoMc1WPMDMXsGFVgo7iv1S/wTmrAoXoIRYsOMKKnPn3/+WQeLDq4OpMYvrzhOsC5fRoB6eJHdx8+yX/SRJn36dHiVr68LIf6tW7U+CuKcIbOB5Xz2wln9UgeLfm/G5VFn9tU3WXPvmDrHMoKFE0IYWHdO6NsCja62jRNCzhUFXwUWvgcK4QxlWCX5ygg/KdY/2QgWKsV4NbhEunOcxSzGCVZBAXHWhb5uba0rMBDhWm4Yympoq5CrbWpyn79nD/eM5uROnDAXWL1pF/rqGvhXB6u/vZOGZwK4jpBIfWbHnsOuARfOzOXs63vZiJ8zO1hIh+tgUb8gKChIn8YqJHjp6GAhTkzChCAn6udkaSOLHRjBwlepOhzsGKLQ45FHHydYOCeoRugZwpQTIoDDDbLIhMKqKvf5MHfvnsZiVJSJwOoIjuzv7un681Rv6nkdrJ6UrO7opM7Q6L7GFtudB8NxMDyO80N21REUgVezP3hsdrAQRlfVMjA0qP/88089aKJEDXOUFUHdGiF4Su7Ak9Kpdg1qYhcWFo4BFpLdKj5iJHYIqXsKrPXrEZTWOhERLiI2HZwWWRQ5Vn4+avqjb4WePkXjzAMW3AxYbc7K6r6mV2QS9qIn7QGhcKPWWm/87ayqGU7FQmO0FHn/UfqWy3l9tQ1mB4tYBkAqud66dasq14Z3ISA+HTJ4IjdHll5PtiCGp0hWMc2YwhvBOnjwYHp6Oh2qBRENPeix8IPkUnRycrRoqNN29KjLZnPt26ct+vtTV0IDDvLi4rSRS5eGJ5sBrK6ohJ7kTJrtdiGxj1Qdqrihu+P+at8ZRtSzFxQzrftYYsfuQzBHiLRkX6fjKH9uLywxO1iuwSKGKOgT48ioSLRJ3inJYsy49VDIGwSeCincQCUBlWNRWIF6QKOCBU/UeuDBcN68eeNUjR4nWL//rvkeeKZSCX01yP7CDQ5XLRL7rlzROlRE6O3lVmF/hieb63WDIRT2nr0MZAMOZ19do+afdoYNWGzdcWkaYTHJ/V09A86hVV7xghQPZPQoY1eKIsYZsRt7MpiOv+7Uv3pBiosaYy3Pg8b+2JPN9YKUgBgYPry4I/SNzMy4St68y5t3+S8dAUvAErAELAFLwBKwBCwBS8ASsAQsAUvAErAELAFLwBKwBCwBS8ASsAQsAUvAErAELAFLwBKwBKx3B5a367z7+VX/5z9e31YveFi2bsvkaJPEY/F3WsuWeX37xafV4h80OZqAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgvSew0BXSVUYxNIny8vJQukLKdtT5paWllZWVqs8cFCVRkRxVsrahoQFZLLdBFLP+6ZP/z8DiZ1JeA1ZAsGXnPi8DCy3k6OholNCMwmsI+SHYFxcXN3fuXISQ3TZpamrS5bgR9ZszZw46bIGBgbrApDI0cNeuXTtt2jRdeE0fnzp16h30Gz0KFqpvyKw1NLxeXLVKG3n4UNNIdpuJYClUI9HV0qIp4ZoKrL4HjwYsVtUc5y8z4sy/o0nIoVdWXmHZs99rwKJ6QEZGBnKgOljo1S5YsED1w8PDk5OTUX+8hx7ekG3ZsmXDhg26HDca8a4hOW78EOKA6LkzkpOTU1VVZVT0cw2qriFeilagZ8FCrBZJSIQhqRugRtjf8nJN2RahUePM5cuRtteKDNC5eRPpeXOB1V9T57icaw2N1Nreg7a4JJfDaT0UZQ0OG+jucWRe9LJQaJSKLCgoAB3VhznkRoFv48aNSucdXHBmulQkVSd8fX3R/nv+/Pl3333HHDZHmFT/ZDewEIVHiHvz5s2eBQuGkPfGRVGiwhjp6urcwfLx0cQjEYKnj8478dxUYA10dDou5NgT/7LuD2fRGnbEfjL59arWNvuZ814MFtmSXv6E5AmM9GkojqJgSzELHSwcFTrKKNUS8i4j4z/CjGBB4fr169nE42D19Gh+6P59zW/hjfTxkWDpbeNGZHw1lWVTgcUB9Dc09VfXIqhqizyuj9vPnh/o6rYGhXoxWHCge6zMzEwV6ZRRV+L06dNotaPRje9hK2RwUX4n9ydDJyEj/fonsGw2GwK4hFQ2R7gbv4Uj9AhYK1dqM/FD9MmoyAn/V7DQTq6p0VTgTZe879pn2RGsJVvPqpz3itSgLTbBZbPbjp/2vqdCI1gkRhSPUP2jR4/inFSf9HH7kFGVCaF2Eiw036k3oSbgh0YW3tHBqqio0DcnG0NrWS/eNHGPhe8hDtLBA5FXjQ0WUbKsTCs1sGKFucCyhvzhyMjWngEBq/BhX/FjjaojsYi/25PPeOXrBjc5bpLrxMREHt9Im3AwOBvSJqPUsR4KeSsBhfghPBCPh0TJ8vJyYzkTtxxL2aZNmzwbCvlCpN5J3jniGzdGB4sbhF2mQ8TEV54+rdU7oal8yxQeK/AAGbrzdoE9PUuDKS3DejBioKe3v6oa4Gj2pHQvA4vs+wTneMjg4+TJk8S7Mm7twVcS8fHxRrBwNrpzgiqqMlGAjlcPLJLFq5oDyoDMWFpnjMGJgIXvoTYOxFApzviCivSc6gGqz/GpjIpU8Pr14bZhg4lCIQ+AznsP+kpK7emZmruKiGZRb44rN+TNu7x5lzfvApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgC1ltaWJhr1y6vb4d2tbdGJndGT4Y2SXTely2rnjnT69sP3zwqWBc4Odok8Vj8neC8eV7fflrUXu8fOTmagCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYL0PsJBszMrK0nVHMbSvVq5cidwoqldIhrrNZwTNyDS0Pgf7KLAxE622W7duuU0rKipCwlRX7uNnMGiQItSG3HcLWtieBmvbNk1q2ziyZIkrNFRTzDIOIqbFoGohIeYCqzEktj01pz39WmNwLIvN4UltyZf09io2w2vAQmMN0UeERnVFP0TYZs+eXV9fj96an5/fSPVHZNN0nff8/HxEH5lZU1MzY8YMu92uTwMpVCEXLVqkK/oBn/q0pKQko2buxMECICTdEYzMzX1jXEkLIqVsHOSb0cB99Ehr3AvmAavpwKn+XqutvJrW32Np3BfXlnzZWlKpWl9Ht/VxpdeA1T9oRqnIkpISxGdVHwIQiUScHcKUHDdK7rgcBnWdd1RuXUM671QbQAMSgT/XoGap602pSFQklSzg3bt3UYr3IFhnziCd6lq69I1BXDBukb1wA4sdR97dhKHwVczZ7rwHqt/fY8V16asadh9j5FXsOS/LsYxg4YRQ2NadU3BwMNDoBVFwQmiT6hqkXV1dUAhnAQEBkejLDo4YZSDdNEipLZCSkrJq1arxKNuOH6z6eq0aQFGRJv2oAt/ChUReF4rPVHVwAyslRZtPhQHcG3eQ2XKspoOn25IuDVjtzYeT9MGOrDxHwyvvS97ddN51sNB5DwoK0qexCvFtl0HcljIWBLiEhATqDJClGXVK/wksZHN//PHHUUXh3w6s+fNRScXRun7/XaskUFioDSYmaujQGQkWtPHl/Ev0RJTbbGA5ahsHHE5L8bP6gCOvBwOO9LV1tqdf9W6wiouL9ao4qampSNzqQXPWrFkwhwS3j48PlUuys7PhiTlqAq6rkKv6D2Dh9rpRyh40FL9J7DwFFvVZiHf8zYKCBkXktWs11C5edMXHazVz8Ex+fqNsqNTsqVVhtqfCxr0xfV09HWeuqcX2lJz+7t6GXUe9GyxiGQApCHh8UxLIZPSQ8XTI4InsG5lkPdkio+LZkFVMM6bwRrDI7pWyMkLLJG0eDIUcKAzRQRqZOk3cFxkZrxtgofxNyINkfBtzcFexsVoHTwaRixebBaz2tCvWJ1Wq72xp77xw87UPq2/punrXK99juem8U29i/vz5xDgyKorFkbxPnz7dWDVOD4UUQeGpkNBG1QmVY1GMzt/ff9RQmJuby8dSEoz0HwQ9CBYPejgqnvKsVtcff7yxSg+FPI9mZ7/myWLREjLuHeS7TfRUuP8kj36OumbHy+a+1s7GkBMMtp44R2TkNcQkeUGKB8JLGR8ex5hssViM2I0x2RgQPfseCw/IS6mRyfju3VpNFDpr1gy/0PL11fTfN2823QtSnv5a4zKBSQ98zX8ktBxJkzfv8uZd3rwLWAKWgCVgCVgCloAlYAlYApaAJWAJWAKWgCVgCVgCloAlYAlYApaAJWAJWO8fLH6Hrn4fLGAJWJ5pwceryyumNDc387cSApaA5alWm3qx5trtKfz5Q21tLWx5r98SsMzjq+rSLlU/KasPiZ3CheFnUvgtYuILMbEJWM2L6sbn1ZaqOltl3X8BQzMrHPVGBhkAAAAASUVORK5CYII=", + "description": "Displays time series data for one or more entities. Data for each entity is displayed in a separate tab.", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -48,14 +55,16 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"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', amount = 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}\"},\"_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;\"},{\"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;\"}]}],\"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\"}" } }, { "alias": "html_value_card", "name": "HTML Value Card", + "image": "data:image/png;base64,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", + "description": "Displays configurable HTML with ability to inject values from the selected datasource. For example, display single or multiple attribute values.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -72,6 +81,8 @@ { "alias": "simple_card", "name": "Simple card", + "image": "data:image/png;base64,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", + "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", "sizeX": 5, @@ -88,6 +99,8 @@ { "alias": "label_widget", "name": "Label widget", + "image": "data:image/png;base64,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", + "description": "Displays static image and multiple values of selected attributes or timeseries keys on top of it. Position of the values on the image is configurable using advanced settings.", "descriptor": { "type": "latest", "sizeX": 4.5, @@ -104,6 +117,8 @@ { "alias": "entities_table", "name": "Entities table", + "image": "data:image/png;base64,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", + "description": "Displays list of entities that match selected alias and filter with ability of additional full text search and pagination. Highly customizable using widget styles, data source keys and widget actions.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -112,14 +127,16 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"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\",\"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,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_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;\"}]}]}" } }, { "alias": "entities_hierarchy", "name": "Entities hierarchy", + "image": "data:image/png;base64,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", + "description": "Displays hierarchy of entities based on their relations. The root of the hierarchy is defined using entity alias. By default, displays entities related using \"Contains\" relation. You may change the behaviour using advanced settings.", "descriptor": { "type": "latest", "sizeX": 7.5, 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 140004c37c..c15a72b296 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "charts", "title": "Charts", - "image": null + "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." }, "widgetTypes": [ { "alias": "bars", - "name": "Bars - Chart.js", + "name": "Bars", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC", + "description": "Displays latest values of the attributes or timeseries data for multiple entities as separate bars.", "descriptor": { "type": "latest", "sizeX": 7, @@ -27,7 +30,9 @@ }, { "alias": "doughnut_chart_js", - "name": "Doughnut - Chart.js", + "name": "Doughnut", + "image": "data:image/png;base64,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", + "description": "Displays latest values of the attributes or timeseries data for multiple entities in a doughnut chart. Supports numeric values only.", "descriptor": { "type": "latest", "sizeX": 7, @@ -47,7 +52,9 @@ }, { "alias": "pie", - "name": "Pie - Flot", + "name": "Pie- Flot", + "image": "data:image/png;base64,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", + "description": "Displays latest values of the attributes or timeseries data for multiple entities in a pie chart. Supports numeric values only.", "descriptor": { "type": "latest", "sizeX": 8, @@ -64,6 +71,8 @@ { "alias": "pie_chart_js", "name": "Pie - Chart.js", + "image": "data:image/png;base64,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", + "description": "Displays latest values of the attributes or timeseries data for multiple entities in a pie chart. Supports numeric values only.", "descriptor": { "type": "latest", "sizeX": 8, @@ -83,7 +92,9 @@ }, { "alias": "polar_area_chart_js", - "name": "Polar Area - Chart.js", + "name": "Polar Area", + "image": "data:image/png;base64,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", + "description": "Displays latest values of the attributes or timeseries data for multiple entities in a polar area chart. Supports numeric values only.", "descriptor": { "type": "latest", "sizeX": 7, @@ -103,7 +114,9 @@ }, { "alias": "radar_chart_js", - "name": "Radar - Chart.js", + "name": "Radar", + "image": "data:image/png;base64,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", + "description": "Displays latest values of the attributes or timeseries data for multiple entities in a radar chart. Supports numeric values only.", "descriptor": { "type": "latest", "sizeX": 7, @@ -124,6 +137,8 @@ { "alias": "state_chart", "name": "State Chart", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAB9VBMVEUAAAAhlvMilvMymeE+nNVDoetInslNq/VZqOdpuPd3d3d5p5Z5suB6enp8fHyBgYGDg4ODqoqEhISIiIiKioqKuN2MjIyNjY2Ojo6QkJCRkZGSkpKUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKCgr3KhoaGioqKisXSjo6OjvsmkpKSlpaWlwdempqanp6eoqKiosGOo1vqpqampsGOqqqqqsWOrq6usrKysw9Wurq6wsLCysrK0tLS1tbW1wbK2tra3t7e4wau5ubm6urq7u7u8vLy9vb2+vr6/xbTAwMDAxbjCwsLC3ejDw8PExMTFxcXGxsbHx8fIv3jIyMjJycnKysrK5vzMzc7Nzc3Ozs7Pz8/QuDnRzcHS0tLT09PT39HU1NTU6/3VzLLV1dXV3sjW1tbX19fYuTHY2NjZ2dna0Ira2trb29vc3Nzex4De3t7f39/guyvhvCfhvCvh4eHh6Nbi4uLjvCTj4+PkyXbk5OTlvCTm5ubm693o6Ojpxl7q6urr6+vswTTs7Ozt7e3uvhju7u7vvhjv7+/wvxjw8PDx8fHyvxX0yTv09PT19fX29vb39/f4wyL4+Pj5+fn6+vr75J37+/v8whP8/Pz9/f39/v/+/v7/wQf/xRb/3HT/5JH/9tz/++/////APs7XAAAAAWJLR0Smt7AblQAAA1RJREFUeNrt3dlT01AUBvAEd8UNl2oLrdrFolZArUul1hWlVhQXFAUF1xaxIqi4FUQUrDsUClZi4nb+Th96S9NQkjDOOKZ+3wuZw8md++M25OXMlKOccJQnTUYocdRqdw8UAqTfOjG4lvr7uowOqbtAtG6o5OiGFoNDapuJHO8tFK4xOKS9msTVgoUiaQjPclnSl01snZ/y4ly2yBNZbRarbc+3yvdpt/hLawOPJp9ur7O0mRiEmzFkY1M6N/4I8qJpulzR2sBx1sgRjQuyA2pk+STdylw2VqR/FPFfWdcCvjhduiM7kVF2db1Nuspu7JGes+I76SRba7f0Wfnn/6F6It+mfo7m8TvYvh7KTiT3PQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggPwXkFa7e7AQIP3WiUFzdl7LuBDFvJZxIYp5LeNCFPNaBn7Yc+e1KlheSYcrFCniL7LZqPl8cbp0TjavNcqu9rZJB9gdPdIJVnwjbWG1ndI95UzWM9V5rS9Ti3P4VWy1+5jXAgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAMK+FeS3Ma2FeC/NamNfCCxEQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA/hZkTKSkwSGn7VUJOmOLV241NiRSRZ2uYcvt+Io+Y0N83USm+MqGWEnU2JCqXqI1ZE+QhYiIy36tXR5INpOQbB5kftcmK85mtdey2iJekeWqX6/3kc+TLCQTrq6eEuZCgKTsbnNXBsIZOERJMedNQnry73XpW8UAKVhIyBPScVfQE9RqEaP7iMT6g+pdw7vKbxKl3IJqV8K7OUqXPHvG9UMGykk2lz1d4g5yvNXo6QqZiA41aHT5OwQTUWBJSrWrvlMwj1jFU2f1Q8K1dCysCUmVhdenNLvMRKZt3hGNriEnxfxlGqt9aPYRkb9bP6Q1SMFrmltMukKuMT2QpckWjc/WhP2lYB/TgjzdHyCKVM/gGXnsp+qY5hbba+hIVA/ETL0+9SepsoNiTs/igGpXZIhKqVv9QVJARIfXIWqfiM1nS+qBnHdae1V7Qss8ngEijRO5a6sMCAtdqv+HfgPwpNPbU6ipOwAAAABJRU5ErkJggg==", + "description": "Displays changes to the state of the entity over time. For example, online and offline.", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -139,7 +154,9 @@ }, { "alias": "basic_timeseries", - "name": "Timeseries - Flot", + "name": "Timeseries Line Chart", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAXk0lEQVR42u2deXwb1bXH+Y+lfY/utK98WkogZSk7LcujBF4pAT6lLaWvlBZeCrTw4AGFvrbQkPYRwlIWfyCr7WA7XrN4wzZx7DjxIu+2vEteZHmTJdmOd8faR9K838y1FUWWZVmaGQM585mPPmNZR/fOvV+de+455849iw/7OOuss/gojpaWFhI8cwQFVlQqVbp4JCUlzc3NaTSaveKhVqsJLBKMHCx2eDyeuLg4r9ebl5c3NjZGGosEpQEL+qm+vh4XKSkp0F4ZGRkWi4XAIsFowdqzZw/HcbiAurLb7Z2dndnZ2QQWCUYFVnd3d1FRkU914XV2djY5OZnAIsGowIqPj/cNfOXl5bDiY2Nj+/v7/anC0UIHHeEdwZUQxkRY8WS8k6AENhb5sUiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosECSwCiwQJLBIksEiQwCKwSJDAIkECiwQJLAKLBAksEiSwSJDAIrBIkMAiQQKLBAksAosECSwSJLBIkMAisEiQwCJBAuuzK5jV7SKwCCyJBd+pd35nz7x6xE1gEViSCb7XIFCF84lCO4FFYEkj+IFaoOqSuPn1cfMX75nvnvQQWARWtIKvFg6CqnWx8+83Op8ptuP6hWN2AovAikowsd3FqIppcDaOuIv7uUtj5y+JnR+a9RBYBFaEgvs6BKow9r1V6wBV7PzdYRve3KJyEFgEViSC6VrXxSJVL+YN+6jC+ZHOBQV2Wfz8uNW75lXVTni2q50/zbLWGN0E1qdAEP4q0AOqXq92ZKi6/cHC+auPBKX1dp1zTarq9vBweWytctyaamETVZy3pFim7V6FwMKOX2yPQmwup9Pp0tLScnJy2J5NBFaII7uHA1Xora3Vwgi4FKz9ncIQeXWC5aTTq1hVwU1OD/fMUfv3PjzF0w1JlieL7HcftOIaF0qA5Xa7ExISfG/t2LED79SJB4EV4ijs4+BWQD+9orIzjJaChfMnWUJfxrY45a6qYc6T1O56pMB2ady8j6cfpllfOOZI1bgaxMpgSnGVSNtbRf2ygzU9Pf3mm29iYybs+wUthe2Z8KbRaMSOmATWcgd6iFH1t3K7j6GgYCWIs8Ub91nsnPRVbWpu0Yx7Pmh03i/iy851cfP3HLS+WuU40s8trc/OJsHTtj52ric8H1vkYLlcLrPZDC21fft2QIZ9VvGmyWTKzc0lsIIeRwc4phX+Umb377OgYOH80X5BSaRpXBJW1eXhXy53XL131scTBtxNH9t2qJ2Vw1zQavjOxw8LPrb7DlkdbjnBws6XBoMBF9hVdXx8fOfOnbjGRpilpaUE1tKjwuD+brzQkc+V2AM6bDmwdoju+NvTrW6PZFVl4UicN6dY4YyFF63O5A7Nk+8Eed9PnIHstmqHjGBhp8Jdu3YdOHAAJjx2kysuLsbWvbt378ZemP5U0X6FOJLKdevjTqJLHs0cAUZhnukV3awjPyjRS1KNA6qudXtOrgMZhYPhV8P/fLe4/5LYk5jMJpTp5N2vEANi0GvSWL4DHqDL9wpKAhoiqCZYTmPhfLvOAcG7D1g93miranV578wQLKo/lTpClBj6hOAWlTAg3rDPsqKbjfxYMgo2jbqvFOdTyFloWL63lutIjFPf3yeIlw5xUVZ1c4XA6J0ZllqzOxqw6s3un4omPywzL4G1JoLNY27mE3rssB390bh6sHD+X6UAxC9ybNFUtXLYjfELqRPZ3dyKJYYGC6/HBrlrE4X7golGYCkniN9xy5h7W42DUYXYXwiqVuxmmMyYuOF7GszuyKoKz+dNycI3/KPSEU6J4VR1T4sTpGI60jnhIbBkF+ydEpxDd2Sccg49nG+rN4XbW8udfy1ziGrPFllVnz0qWEX3HrL6+I4eLJxPiRk+Pz5gtXEEljyCjKd/3zd9KhKyzwJT3ee5jhKsMgN3+V5B5cCrudqq5uo4CF7xoQUe//BLDKeqNSb3hnSLtIkYBJZwwAeNJKr/2G/1j6yBpzRtWDytqpuZ1nm+xL6qqo7Me68RjaF36hyrLTGcqsJiWy+6fIsHuGhatc7sZhH3Mxos/bQHmcQYAnw8wZLFuPDGkYHQtlQ0YBUtJgAitBdmVWHq/bZAyJJArkQEJYZZ1deqhWH6+iTLqCWSePngrOepIjtrRrTemQWW082PznvbTgjxsnsOWf2DIU8ese9rdzGeJOytEBGVVyocYd5jkhhtBPTHBzn5wIJu/kWugO9v8m2nnG1el3CGbNVZh/f1GgdTeBjoETxFVFResMb82JcbLLTFpM0LIwnauEDPJXe4/ppvQOfhZ/TLXBuGuasSTuWQsBNx/scL7QltrgCrXG6wPupdSAA8seiZDHGPUKvMK4vpW8QlhikIcK9LspyWi9H7DF/1Rb7jXn74XX6+GaD5V5Xz8Cka1/WiCO4IjYkoaqO46E1esKC64VeUFawdTc6NB63wPbK8qNAnxiAYT3ekWx4rtMe3LhtckxssnL/OE3TDPxcTAJe7R/QcS1tAnylT1Q/bhGxYhNih13nj+7zq7NPO2gtnau/jRxJ4x3DZEHfXolWK1KDM7lPaVF6wgD8zC+QDyzzvZfFgdkIbIyKLjJEHc23wJm/KHEHqyHv1Tnj/cns4mKVhWk4KgHWgU2gcuMfmHN4Q9xgjLlG8KcW6XMKCHFV9tkQYqV/K+YivPI9XncOP7uPHMviex/m6i/wh6z16TVLu83/Oyo1tnAr4BhnBwsDkM2KqZUuy/psY3IBNUAJoTGvAx3KC7QbdaMfLjSOh0ldYOGVXk3O5e2wdcyPlC5oYzg4lfwPwPjyRqZ4v+4oAkOGN0zy009pDha+XHb7fWv4lH2Heys+fbLzTpHmta6BGbXbICxZSY9m46wtiSA4WZlXQ2Ehqy+91rZXiWU5wuvnXaPHxtqcbR5ZlImkxARBuyaX3iDeZ++OPxxwKK9cW45i1ej3qX1DwcIF+wXJH5hYML2anAvfnik826yrN2q3zDbd7Kz/ng8xd9dWZpgd554gsYCEHbUO60Chv1DpYKAqRV8nB+suCF9u+hiNaUMHOgXphBBEberLlUfYjDnqyDHQopKX3+HcxsIjE4hqToqO22mw/qb4LNR+tvPmKuBOYLxtPemDVsA7FeVfKBOwKf5Em01xP37GRjs2W+ptx457K83mPTRawEtuE3yKcubBpYOWwZEUk0UoIFrwm+N1cFj+tbXt/qvm3zaaZTwxYHH7E6Jjp5l+5q74iXjykHrEF/fwuMUv4tjSLurk1aKQ5q4dT2BycaP096uysubh12PSQOMO4cnFFBiY90LKhS2wbNuj6imUZCi0u741ifgimXWzAZn/uOq6XECwsXX8k5bC5/FqmGGz117cZBj4JYOl7s1AfV/WFYL1zoJGr/gb+nG26v8k8v/TzcB1BJ6Fx3i/p890azHm2WmuLyq7wPGO4813U1lP5hc6BBvxZbuBYqg/0FhRE+E4+WcBiE5n7Mq2+Yt6sFZTWbUlTbq80YA2NaisL72VI2WuvstddLfRlzbfRkWsL1gGVxl57JSoz2B3P3tEONnPV38Q7c+q7m0yzS0XeqRca5/bkKV/bPF8SGGlWBqxefQGvOhdjmb43x/cmVnj/6bjDf066NmDBRckyTDK0tq6BamPnWzp9Yb3RCS8A3sQqvGjB4qZ4/YselWAtWisuMHR9AJug2TQ517hR/Kl90b9RlAerSfV3QX3WXeNvV2mGNK5qYaKOIbLZOLVcAiBSo3B/Bb0c81/DalbSM6IZbGcDt0n7RvQlSg9WjKpn84Hddccecldd4JspQJc01Lx8R0IH1L7LEylYXk7wy9UKv35Pxbl5BY+W6Uf8TE7HifbnxOLOMWtfWxOwwLdDJdx1r/7jgH+1G/odtZcJP4b6H2DOFZgAKJqhD+TYEKi4TpzrvF3nUGwCK04Dxx11l6N6Uy0Ph3aRKAuWtZs3x/KdD3lqvuHvQLPVXTve+hSGqgVvh+qc+iN31jan8B7rqsGaOsqrr2Hf01V+z8bExv85GsT+MHa+Iyrzs1EuNJnCYGFahKIxpVrGqh1ko6St/rrWYXNAAuCV8XPChEv0LyAAFV6JLoyz0d+j2mydb9ggKNTG25rMFkkaJwqwnKP8eLYQSKq/xB+mE6XfUpc+NNi9t214yFdM56AaHh1nxaJLrfqrfM9j/HSpGLZf6bD1CjEEJth45XBfFkt3LB4I/sPS9+ZiQBRsmsaNWapGxcDCbEgs95wAO8//bB0esdbdgLo5ar/bbujz/9cT2WZfOLxkgFsRqT5dJgZcfNWM6qqBnlS12RnxPeJHKM42LmobHpaqcVYP1vghvvdpvvHy0+JHdd/muzeN6JM2JHRihlzUH7xdDqmaYvLjmotuOyWovlaIRrnGg5fHTfN9/8tXfl74ZM3XhE96nQiZregzxIwGg6/AlurydoNeGbAmWzYJNrvqgdAfazGesNbfJMznay9tH+o9FaEr07EEQKxADCEO7YJpAbhcdHl/zjc49OvSQzjMlrtHUcef7an6knawRcLGWT1Yi34/vvrL/hFv/PcRMWEoRJejQvEtwoTxgfQ218Brp1QdAlL4Kug/X3rGgjl1ofiBc6dqf8Y7T+DtjnHPxaJhuzR7ZIn+GGb9h9l+d79KbrDQK6gnHIN5qrKVTTHjFHN0uWq+BZPZVyIidI8V2kMitRfuJdZoMInwJzyTtap3HbVXLLxZu97QtT2c4YyVqNMf8YrRwD7dIWkbZ/Vg9T7LG2NEmE4zwrHgjoVUkXobukIbDy4GyEDP5Me89kHeFxCA5ht4hR/P4tXXLbzTvpG3dPhmhYgrC4vpjjvCM6VnzKofiVPF8/t0+2UFC64EwVXd8XKYgvA7zKl/LHL/TaYqIIiEk4pgrddsmsbkmvksBPO/7kaMg75IEQRhTQ707LPXfo99APAZunYEdZv5n4WqfE/Vl/F5fLnkjSON8Q59hRmN/0NXQlQI4Qt8Elm2M45FA8s5xpt28k03nja8Qp+NpfvPCptH3cwLHILdwEcIqbRj7S+yqSIs6xXnO5GBhTmggEjV11tM4+ELQgMJMTUhuHZB10BtUEFM1jDD5aoWpkSYUYpIcctU1QVHlBhUOZshC1lMVJcbkedV68Rp4G/CbxalwTrcx7Ek8SpjWBVij/WBHzXwi+bqeN1TfP13+OH3eE9gdiVSGCCFVS6rbQJD105R4SNstwndKS1YsJph3+DLMQatlkjEeRDtEdn6yjFVeoCZD/3HNAqbaSIYF15VuV59nqXh1gW8qr4OvEB8ANNsLJ5v+GGY4+YagIWnXLBJsv+zN0NXCKlIF4u6ZzK89bcAC4+lY0NtuYGLoAkQvXJXf401ZatxVEKwYOiIxs1lDNnVjqHgcqL1CYEA1fmoJPNKQMuyia1Y4Q09faURVLWrv2qm6efsSwAovrPVaFycBj6JNy2qi4CvTDMbCcDC4zfR5bemWle15u5BMbcaidJhgsXyLf2fRLXaJtAOtTlr1jGzt8PQJQlYMJXY9FMcoSJ2gLngixGneP8K/6RX9S9syjLd/J9w00TZzd39lbNNP1nAq/ILY20vjHS8wlCDjSWfLyZasJAzjwdaosuxNmFVFcJiI5b0jaUNK5abVNbDHDwrPvYpdBMgXG9puIUNEJGpgYATowzTgj4zJVJ3JdetenzRg3DeZMt/aQY7JOxmuNamm355akavOrdXny+r9zhasFjWx10HLBEs5nw4P9xHVW9MnQgz1L9iEzSZT0ITMA8Qpk7RtB0whftHiAQMVEsSYDFrXp1o/QNST2WawAJWIAtw4buSO94VFVhY9MOS+LBqKoIKIe0TyZ9IAfVfXrf0QMY+m0WuVl0t3wQucTgQfr4TrY8HtV7Dabvxtv9mSVefkJzVsCMEQwqUGBVYsJDE0Kk14gptEp+vj0TQEIX+PFv4DMK00jYBxgIk0bI5fASJXJohLXQecr07DD2fLrCUEYwcLCyWxWI3TO4OdkXuHCrsc7E1wVg6F7RE+AwF62rvbLVR+iaA15tlHHDV/xZgcq3YdkjcgyCmWp9tPtYArD+XCjE7hHGirBDWH7OHeQb1u94nrvN5/iOjTE0AB8+s+j5mchk73w5TsKevjDmflnouCKyowOqbEfLNcX4cXjJaiAohSQGpCtB8Sx/OVCj6XZHWnFLRI18TwJMkprucwzyoLBISUtDFvNv+IBJY0oD1e1HNPF1sl6RCbHnkH47Ygy5LRFqzAm3X13uQTfEQuoaLMoQgclRYPA4TTAJLSrCwipKlGBwblCbGhJVhLGMEz87zlZUvZuj+IDnax2yuIkNhqI2loyDKdlyVFtwjarY4a4SkDOSonAl8KArWw3mrjtmtWCEkLDCLbSFM5F0IE2GhgZJth6DvnPpeMdn1vKAjHRIBWH7BcstQCawIwTomPo4BTnCVgZOwQogAsg1eak1CnbLFVdS3pCyEiZRsu0WTS1xr2vo7/+QTZASwgONy8WACK3Kw2DSN7XQlbYVeLrezdG9ucRU126d0TdquWvU+CwMLQethE3vzRNsfhVTgpp+dOXwoB5Zo96yw6DviJwqzRyE+J9ryt6VJ/PzW1Qoi/46ZXEgGR74U0ogRHkY8JHQUj8A6DSybzZaZmZmamqrVavGnRqNhexdiM7AAsGC2v9fglKlC/xCfVsDOD/xSv9eq7bBOiz3FAHkHWGnNFv+cUXxEC1Z+fr5er8cWmDExMezP0dHRoBoL1k9kD+cMp0Jwr98gLt3ckGHxL2UN2w7LE3wmF1JNfMMigbWKoRBbNSUmJvLibqvYpAk7geGdALAiq0r4FXpdfL7qrtOTcNa87fp1GTC5TJqtZxofEoDFNlmdmJjgxV3m7HY7tpXLzs5WGCysN0dOX8Mnr+2Qcxf0yQsEViiwsJXc/v37MRoygJhphT3lkpOTFQaLsUW99RkBS6VSbdu2LV08oLTKy8uxjS828O3v7/enCkdkW+PReaadp+1XeNoiZI6DGltqvCugsUjwTPRjEVgkSGCRIIFFggQWgUWCBBYJElgkSGBR25EggUWCBBYJEljUdiRIYJEggUWCBBY1OgkSWCRIYJEggUWNToIEFgkSWCRIYFGjkyCBRYIEFgkSWNToJEhgkSCBRYIEFjU6CRJYJEhgkSCBRY1OggQWCRJYJEhgUaOTIIFFggQWCRJY1OgkSGCRIIFFggQWNToJElgkSGCRIIFFjU5gEVgkqBRYOp0uLS0tJycH+1MQWCQoGVg7duzAZmB14kFgkaA0YEFLYXsmXBiNxry8PAKLBKUBy+VyxcXF4cJkMuXm5hJYJCjZULhz5068YiPM0tJSAosEJQOruLgYGxfu3r0be2EG7FdIBx3hHkE9CxgQQzgdeGUPKvHTWKLS90PHGXIQWHR8AsCqqKiA+YVXxepXVlaGEvEqd0HYqri3t7egoMD3Z3V19ZEjR+Qr0Wq1ZmZmpqamYp7k8/WkpKR4PB6ZSrTZbIcOHYL3G3eKP5ubm9G2WVlZISwfJcCanJxEK+ACNz81NaUAVdPT04mJibjA68zMjKxlDQ4O4gcTExPD/iwqKqqvr5e1RLgJ+/r6gJGv0MOHD2/ZsiUg4CHhgZ/KwMAASty+fbvdbt+zZw9+P1VVVQGecKXB6urqYg6I48eP41oBsNDEmJyirPj4ePma2/9Ai7MLdPbBgwfx+0YHyFqixWJJSkpijkOghvuV9U7n5uZaW1uzs7P9+TYYDGsJlkajYUMSXrVarQLd7HA4kpOToUjw6nQ6lQRr69atKL2jo0PW0RChs4SEhImJCWgRXOAe5QYLAZXCwkKoRvZnd3d3gBt8DcAaHh5mQR5UBT8vBboZ4fCSkhJcHD16lJkFioHFLkZGRvx/3JJbdfv378doyNQV9BYsns2bN/t6XfIDGEFB4mLXrl14haKSz6Q7a1UNAVsHowNeca2MxkLgEl2LV1wrCVZDQwN6GlYI2JKpLGjibdu2pYsH7Ff2pqwaS6/XoyVBM9gFTy+99BKGAplmY/8Pl7O7ukBGoYYAAAAASUVORK5CYII=", + "description": "Displays changes to timeseries data over time. For example, temperature or humidity readings.", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -155,7 +172,9 @@ }, { "alias": "timeseries_bars_flot", - "name": "Timeseries Bars - Flot", + "name": "Timeseries Bar Chart", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABOFBMVEUAAAA3oPR3d3d6enp8fHyBgYGDg4OGhoaNjY2RkZGSkpKTk5OUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKChoaGioqKjo6OkpKSlpaWmpqanp6eoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrK0tLS1tbW2tra3t7e4uLi5ubm6urq8vLy9vb2+vr6/v7/AwMDBwcHDw8PExMTHx8fIyMjJycnLy8vNzc3Ozs7Pz8/S0tLT09PU1NTV1dXW1tbX19fZ2dna2trb29vc3Nzd3d3e3t7f39/h4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fH09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7/xx////8KXFhiAAAAAWJLR0RnW9PpswAAAvtJREFUeNrt3GtXElEUBuDpZlAqylVHCyMwyi4SpkaWAl4qk8wwUhJlmOn9//+gLzbKbc6AAm5991p82WvPOedZZw6zYM3aGupCQ4tYkZDSsKDrocNVvz8jHQKYY1ZiT/6OAJllRPSpsnzIqIG9SjYmHpKfAzI4CIuHjJeBtyHfZwCatiI1/m+BYV2Dw35NniOEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQUp/6Wx+DgbRbxOAhEb/fXJ98akiH1AIAHtSWlqRDSvdDGcOHnYR0SPV7zVsaQyHeO0jTJapEV5C9bUz9fIjsvHRIefhRxHqpjxzA3ftajXOudHGJOtHV+1rV04/wHenDc2QQkFbLJISQwUDaP90IIYQQQgghpAvIxRO9gFxG4lZ9XBFIx6sihJD+QVRjEEIIIYQQcoMgahkhhBDSe0iLX3ydQdSLIKR9ghCJkIYEIYQQIhPS8d+AhPQboppFw9HMxBrs/lqCIbObpgezP67YjnQ8iwagHDzrryUZUgsUz/prCYZY05vn+msJhiwM6XrR7q/V9OJU4xQuEpcxRsezNPbXEn3Yzz9Hulg3IYT0C3LxxI2CNCQIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQlpA7P5a0iF2fy3hkLP+WsIhVbu/lvRby+6vJR1i99eSG6ffu1XHxlRNcfWq3I0iIK4tJK2nXVyV0lOqEmvjGWAtvnCu+jMzkQWMiOlYdRSd3MAH/UnVPWR/ApFfSkcpiOBvRc3XtAeYW1ZUJbZMD5C8azhWLX4xvZVR692Se0huHm9ySogxkhs3lFVewPM4WlFUlUMoJEYUox1+jAFIbLuHrKaQWlMu8TicDp+4gdw7/qS4t2qBohk4UUF2nieBfLyDM/ItgXhBucT113i14QbixW7M+SRNb6EQ0u8kHavyZQxj2/kgNUCsYDRoqXfEF/Mdu4G8D43uOtakh3R9H1DsyKZvOmneDjt+D/0DTzolrPMHmggAAAAASUVORK5CYII=", + "description": "Displays changes to timeseries data over time. For example, daily water consumption for last month.", "descriptor": { "type": "timeseries", "sizeX": 8, diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 630b9f830a..a7d595219a 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "control_widgets", "title": "Control widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Send commands to devices." }, "widgetTypes": [ { "alias": "rpc_debug_terminal", "name": "RPC debug terminal", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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", + "description": "Allows to send any RPC command using it's name and parameters to device. Useful for debug.", "descriptor": { "type": "rpc", "sizeX": 9.5, @@ -24,6 +27,8 @@ { "alias": "rpc_remote_shell", "name": "RPC remote shell", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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", + "description": "Allows to send emulate remote shell. Requires custom implementation on the target device to work properly.", "descriptor": { "type": "rpc", "sizeX": 9.5, @@ -40,6 +45,8 @@ { "alias": "knob_control", "name": "Knob Control", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAvYklEQVR42u2d+XsUxdbH/Z/eX9/lXoHMFnYQRMEFNwS916ugiMjmgggIKuh1ARUUUGSRVRRFcMEFyJ6QhZANQgJkJ5NlkvdTXTM1NT1JLl3dM5mEnKefPJnu6uqq098+59Q5p6ru6h+j/v6+vkhfX2dfpKWrq7WpubmxsbHuypXKysu93UW93fkcuXn5585f0I8cThUUcly8WErJuror3MW9HR23enp6+vr67nCW3nWndrynu7u7pbX16tV6kHGlLqe3O5ejtiZHR488yWFD1W0e1Ez9ra1tPOtOg9pdd5JY6rt161ZvTxVyqKc790JOHAHVVVFg1V/1Eli2o7SsrPH69XA4fCeAbNQDqy8S6URD8VLl221vjWKouCT+yi9dip683phCYOkH2hNJFolExoA1wigS6Y701vV2F7a1JmAF6EisVFbGz5eWRjHU3JQmYOnqEtyPPhk22oDFG8JyKi65mJN7oadLYAKtl5sXf5FYURIrVzWtV1QUxVB7a7qBFZealZWdnZ1jwMpASLW2tVUxWlOvqqUpiqGKijhcysqjJ5tuxk+CPHkyfCt3uIAlD4aaN2/ejIx8ATYagNXdjUlewuvv6kwwyWtroxhquBbHUH6BwlD85PkLcfHG/8MIrPh4oqYWt8UYsIaHcBoVFhULk7xtAOFUUhJFxq2OBAyBP3k+T1ORyKrkk8MIrJEOrxEKrL7enpsYUsmW080bNgxFzxcUxt9WS3P0ZGlpvLAaLWJvZQ6w5IH3dcSNH0cesCKRVqn4Ll68oCu4HgsBaDRd5Ny4HoVLVVUcQ9fqoyerq+MnMbmS0ZYhwJJHQ0PjCBo83jWiINXHt6tgoVvfuqmuY6gq5vkEYcknGxpykt0Q+LQyE1jStG9vbx8DlpeEE0GO+AoKooa2UFvFcaZfrowio60ljgw0oDyJXaWsckRdckm8D1ExVpW5wJJHeUVFb2/vGLDc+xE6rl8vG9DJqTvKcVx1h6PnCzU76Va7crXnqJLyDOUV2pSVRtwww4ElD7wSY8Byof56rwgEdOViRen+TGVR6Va5ApwODnwN0QBzbfxkZ4c8mVddc6m+vh7fN3kNfRE84O19fV2aKdOn/RXeVwhpQVCZsCN3cS+OTTIdhgVbxKkyVnRlLrAYB1VevtzaMnAUT9na9fUDOD/BjRJFFbE4IOEaEFBdU9PS0hLpbe3rCyvEeNLarq4uasZBkE6cIX7b2trGgHW71BkOy9eDL6onpo8uXoxj6GJpFC7d2jAQPnfeyokVVgZvbm9PeSRyDZWazq+C911dXaNHAlLqj8i0AWMGAquvtbVB51pjTMERTtbd4q0xd1SdpvjqYzZ4Y2MeegopMuz9oQ20JNVirKi4JKN8XZkGrJ7enjKCLbovKi8fqzwqtC5fjmMIJ7sa8WGPy5Mkw0R6KvoiTUiNTPtiyMSqratLHbbAbmdneAxYA6iP8K0i5aPShZOK+hF4URiiQEdb3NVZVFSMSyLzXYgi/6KlpciKRKXiaG5uGQOWJql6esgfL7kY91Hpfk5Cy8prcPVKjt131VMc6b0x4kIICLDyikupwBaadwxYURYrIxfcKKtcd0cpxae7GC5fvhyJtPSPZOKLwmHhObZqamvvdGAhbPLz40MnqK01bq1f0BRic1M80sy3jjOpf7QQffFceoHXOxdYfZHr0sOkAwhBpXzo+ohP+EWFoizoizT2j0ZCcntrewHW4TI67xpWPrYqH5Wei6dPm+nR3Feoy87Oa/39vf2jmpqamjz0foGtOwtY5HfrETqbtQ6KlG8ds52RIIJ9FM9pSR4gV1y6NKKxddcwyaqotQ6AGhviwomYjO6+CsdSPbu6bvTfeYTv3ivRVVVVPfqBFYnc0vmFK0GldOLq1BM4y8sv4EpIZygmA0VXWXm5V2Gf0QwslkhgKQQsKt3/ySSZjvZcpfjUbK3r1294GCceucT8aU+wRT2jE1gi5aS7SFnr5xNHgmqOAwIsLy+POPQYpHST1JNoY3tHxygEVlV1dX193Fq3Yau0TLndi0iKSiPcCXu31tbWFhcX//777z/88MOJEyeOHj36zTff7N27d7dF/MNPTnKJAhSjMLe0pjGIhFrU548Yp9l0d/eMKmCR8Sit9SGwRcivt6cUd3QaRg/V1dV//vknWNmzZ88uF8TtVEJVVBhOsZQFxCz64BJb+fkFaRhfpwlY6DXdlTAYtlj0J6VG1bVr186fPw8OEEK7UkBUe+zYMR7R0NCQul6wdlfmO+XTASxyNa9cydMl04DYSp1p2dHRUVhYePjw4d1ppIMHD+bm5qIrU2LONza6xJY1MBrZwGJyaanMLdbnv9uw1dnpfUAegX/p0qXvvvtu97ASDaAZnmsfaVq4OVKquFMOLNInFXrI+dQXflHYYr0hzyFVWVmJyvvy9ujAgQO8/p9//jknJ6e0lKUfKzGY6urq0Gg3LOIffnKSSxQgsklhbuHG23wE8vLixYvezn1gNodLYyt1I4/UAgs+5uTmqnRhaxmFhMmAIkvdU1lFFgrjNUZw//FNA7vffvutpKQEw6vJBXE7lVDV7eD40KFDNM9DeLmUW2S0jkhgqVQQUopVBh+ZC+Wx0A22gpdWbV3dkSNHvhqc8BrgLMDeunr16o0UENVS+cmTJ3nQEM0AXpXemc9YS26wxTy2EQYsEoUTJsGV5igXaI81y88aA3pD2MhnzpzZOzgxWDt37tyVK1dupIXI4cTHi9NriCadOnWKHOVhHyey+nMqFGKqgEXo5lp9np5lJd3rKsPY8ld50B/Mqfz8/P379w/2/n788Uds5+tDElBAqfGaifvyBePmDlvEBJtui/hHnuESBShGYW7hxqFr5tHIyMHaRrORcJ7Y9W7SUD38wlMOLKbKyJUX9aCyDAtaiaBFnnhB8SPw2r4eiPbt28elioqKhkFILMve1ISoAyvdLgi0sVAHVVHhYM9C8Z0+fZomDdjU77//3v2kU6SOG7+856twpQRYkUi7stZJXa+pSXCvoyM8idgQUcFIH/BVIaVIhx8CT0idrkEp3CVkFUKro6urA9hwWP/ws9O6FB7sTjHvfnCEVVVVDfYZ0BGGnO6Ft3E8kTV2RwCwyPTAWleTAeV0LvKrousmuHaf8HWCzn0DEcY7lwZ8r7xyJJzSboNQl3WEu7s6urtau7qawuEbHPzDT3GSS9Eyg1cBDDs6eNyAzWBUSCMHbDwue5dqUaZPmh3eTtX3Hliqb4WFF9TKCzLXiqWn3Dt8EdpSrdgIe+Xs2bOY59eSiDH5kCJKE1aCEE6tXYApXB/urAp3VnJ0hav52SVA1moVCN9ObVKAJbeHRtJUGpzcC7rmcpKIcY5NXn5+RgOruKREd1Mx+lOOBrL2XBrsoBaLZH8S4UPCb1mfRLxXbulyQCi79nDnja7O2pbm/PPndp368V2Oc399ceM6iT214pLQjA7qpAEgO7ltZWVlNDu5L3TQpQvAODcQdmUosHp6burrDalp7x3WYJA9R1z6FL799tvkN4FFVVNTY3ttjMhuU0olSqxb4c7mcPgqqDr72/tXLs/rbv1vDv7hJye5ZBVwXDONoUm2RuJ4++mnn5J7RDfdBBnRp2Y5zdzllevBW2D1sRMEphXLU+lhweiqaF2uFgrjo8cXdTCR8DTinbqaSOgazIUuIxJ2evhmuLO6IO/razUP9d/6L3XwszB/H5dEASG0TIiG0Txbg+kCxruta3TWzdJqPMhMaN24cTPjgNUXuREPKnfkYFGpwaDLPA3YdPz48W8SCVThBLqSSDiWwq6oLdyJqKsoyNvV0/Y/OrB62v47P/cLLlFAFDMlNCONtDUbi55goq2DYMuN3DLL3PJKaHkJrJqacrW0UDTq3JLDAo201c1gh5dBuNfGdAZWxHTrNOL14LTscksYWNc7Oy9dLN7b0/a/icD635Kir7hEAYq5fAyIocF6++mOTFvVCXvLeB8UY4XoiaXlGbBIeJXWOoJKrX4mozfh8DUX1XZjQtnYzQvA8rWhysCiGkIVVpQdbWlcoAOrpfFJTrpUhQkQbm+3YQt3brJgxvVlvMrXzaYms+WQMghYvFx9RheDwe7oYLDAeO4y3xwRwEOJBOuJk9RqhHPoNsf/t2+8Mwasqz3SdmNRpP3/OPiHn81NecbG+2ADRkwuvS90jQ7aukyKjrHILygsGhafljfAYuvbZKnLmjAk97E6g3G1ZEcdTiTMDtwKNRrxYrxCle5uCFvuhpqq76/Vn+Tgn9aWgrCRu+E/es7w1Os9QhiDLVvH8fqa8RBBbgAsokMZAaxIL+PzHH0es/vJ3agGG3PRgBi5+jvglXiKqriDNCwdpOFq6SC1NGB92KGD1BhbdJPO2rpfZ5o7ZbaOjctVNj0BVp+aLcjCQ/pg0NiJTEgEX47OVqx1shiqNJIa0GvqtA4UIsPDZgZwncKW57ghNCAnuRQt4zEhevXeIaJswIIhZhoKiKR/ha27vBBXt+K+devAHcoKH5cqLxmbVkQ2jiTSH3/8ofMd76K3YmPgIHQ4FoQO31YQ2qXcolN6H5lSZmMCbDEztsySatz4HTwAFi41vO3kH+tRZ5HJHjH0weCdOppI5MSRraA4jlNRpUyNJqJTdE11ky7jl7exAi1pNrg2ABa7Vg8nsApja4WRa8VgMLpETE+JsS8UmX9cI5xYjMMVuzE1Uiarhp/AFh1UnS0vL8eVpXPDONpjEEC8WFo2bMAi1yDJdSu214pEDCMDhP2PJ1JBQUFljPiIcf+ERzXxadFN1WW6b2MILEpbRo2xm8MtsDo6GvUZXbE1HYvNamNAZGMis18qNSJ8lro3yogBTxKzTH/55RcEg8wi3GORzMXjJJcoQDGG8alrCd3Uew0TbGypNbKsDdahNM7KdwssMmFwhLLaLKsaqwz3FiNZjfDDyf6tRmgBdMGlGGF/pEhCMH+LZzE96/Zn01OYW7iR21PRKjqrOg4TmPmjcwZGGSQTg5K0aUN3wOrr0q11UvnYpbKkJMdsNEGw7EQigdSKGGFweC4k8B79+uuvTGqQYmn3nj1bt763bPnLTy56+t77502aOiMrmH33BD8H/0ycOp2TXKIAxZjiLO/idiqRHjUPic6iEFX3YYWNOfhRnfux+9I2NnQFLGKpLYlRZzEY7KkwczHwFeqMYyRYrhHaodM7IjWKoTtT/BA8e7788q1Nmx9/cpEvNOn/xmXd5kHhJxYu4sY91kxUqiL6RLUeNpIkCJ0DMMTGH4OJr3inHC+pZbSnqytgMTVBpiAzGOyIzeuy9rFx7h0uL9e5xkgQo1XxFJPCQ0mAkUQSsBRUK1etQTLdPp6Sj0nTZqxcvUaKLqqlcg8lKx1XTMALA1t0LiHJDFJF0jNb2hWwmNqhZyGjBBsa8gx2R0Jc8f19pxFfv2Io7OOj8UoGUDmWOCB45bXXQ5OmuoGUfgQnTX3t9bVUS+U8As3oSYMx4Oi+YgVs0bmE0DXQU05n8uQaJTuYA2tAhW22iwvGxPeJRECjNEbUecsL4sUwskOofLxt25y5D3gFKf24b96D27Zt5xGkJAAIT5qNFa9YAVtsjML0dMptpqemYdahObAivW34q/Sdc40jlwynT2rEd1mqEV4A959+UVHRAYvWb9iYFZyYClTJY0Ig9Ma6N+WzUF7uW073sdMVN5BSOq8MfFoGoUODYb4bYF2NZiHfEmtfMZGwoCDXoB78yD8kEtkyF2MkM/hcEhWSRc6bfnbxktRBSj+eW/ICj5Nrr7lvP0xQDKEvNnYZOOKdZpYaBKRdAKun0jYeZIdcg3oIfulswtgq0UgupuCG5KpGvGY8BelBlTx4HA/l0cgtl12QnjZFBBB1jhlEDwm7OpxyWJA+YF29UoKgUgvIWFFnkxRk0iN/1AjZXhwjxkQu9QjeRTlJYeHT/0gnquSxYOFTPJoG4D132RGZpCUJFukcg4HOtURbqr1Z5sBSg0EWUmN1BibRG2whQU7VKY34FvEyKA7K6aZunFVknPJen1vyfPpRJY/Fzy+VKYr01E1fYIViCyw6lUjXHS7figPMKbCcLoxgCKzkIaFZBr6NR5jtRTHCnsBudaM+qJA3unHT5uFClTze3LBRZv7gNHGjELHcFXNglM43tG2qnQ5OdwQ2BFZyUkN1TY3ZeFBnENl8hTHCYnXziTMyJ1j7xa5dDNOGF1hZgWwW6qYxLgeJpNMo5rCDgc432OiU89XVNSndiscQWL09N9i1Cw1InrvMbjAIg+MF/imRGEMp3lGh8ceNasAxjR6c99D84UWVPOY++DCNoUly4r8ZNTc3K+bw2dhY53QJZKcBaafTKwyBxTrH+niQec+RXseDXmzz0xoh3gtihCXhRg/yQfMW1298KxNQJQ80Mk1CJBt3CoZIA0sS7NK55zSRxsCblR5gVdl8DewS4LQSJkforCHVKT9GMpfBjJiVQLgDsyZ78rTMAVb2lGkILRqGFW/cNdiiWAS7dO7lO1yEKOI80yHiZGBoCKyGhlrc7iTJkN1gpbrnGaxPxLD5jEZ8zXkxAhzG3GeNDWIdb7y5PnNQpax4GsbqasZdA5SKRUhlnXsGLnin9ruj3Z0MgVV5uSpxA0/HE3IYV+KA0VmDT1lxDW9yhxFhOuAz/P7kySnT7+Fd/m28OP7OMSHr7qyscROyxmeJg7SXuyeIk3+3rvI/B3Y2Ze6WJ617dVhE6xkfLTlOlozVEK8nK1qPvCQaEKth6sxZAIvm0Uiz3mFmKRbBLp17MNOpq8np7sCOVu0yBJae12AWewY6vyRSbozcuKpxUvDmPt62PQ4FC0xZvix/MCsY8oWySdrLCoR8/qDP58/K8nPB5wuIqwHr8AXFmfESGQqa42L1UDjokyX9omTWBF8UrFY94lLQupoVyBrvEyCT8JLY2v7JpzSP2J9xB7GuFKMAk85Ap7MOnUajgXXKgWXLnnbq5Oi3Jjr/qpEOLNIQOkwJBYEz+h//ek4CCxHC2w2EsiZO8k2d6ps5wzdrhu+eGb4Z03xTJvsmTfJNnOjj0pQpWdOm+WZO982Y7ps2VZwJBH0T/L67lUjLyuInJ7mFAhSbaZWcNNk3MdsXzBZ4FY+YImrm0vRpvslTsrInZgHZ8Rq2nnl2sYwuGHcQUCpG/ZpI5EE4egVOd0xxtIuWIbBudeRjXZHqjplFll9Xl2NggR4bsHJixPrBZkxHCmLG4tcJTJwigTXOJ4QHuJl1j+/B+3yPPuB7/EHfow/6H7rff/9s/+x7fLNn+u6d5bv/Xh9nHnnAN3+en2L33iNAA1YAk9R6SCN+Ak0KP3i/75F5FPY/SCX3+mfP9M8ArNN9c2b5Hpjjf3ieeMr8ub659wocT5qU5Q/4xkm1OC6LDDDpBzbW9TBHMcoGLFjq6BUw7E7dcvCmfqzuvIQhYaTZaQ2MYnSm4OK7ECO+PDOmE1DjnZEmrMQVyg4ZAxqe+4dv50eB3dsDez4J7Noe2PFRYPu/AwseDcyf53vsIf9Tj/q2vhU4/FXwwJ7gujWBxx8SUANGaMwJlq5EhyLSwAqXNr4aOLg7SOF3NwQWPern9vkP+B+Z53viYf/y5wJ7Pg0e3Rfc+WHgX4t8D8/1z5ophBzNoDFSaJEKSCNpqlkf8RsrRtmAhZZMqcehqqo6DcCy+Rocp0XDl980QoWdjxF5nmZMx82D2bFu/YaouJqAuSN0Ey9423uB1sZs2/HSksDTj/uXPOW/8FtIP39kX/Dxh/xIIDSdtJkmT/bNmS2gc+KboF7yrzOh558KPPuk/7kn/e9t8N+4Er9Udyl75dLAQ/cLzQguhUK0zKz1G96ikdJRZ0AwRzEKlarzkDMuwydDH+VOMqENgVVcnEsiMm530rBQhQYbAuAUOKuRDiwCru1GhHZApZILxfvjLaLCsrN96DsAcehLAYjKwtAv34ZOHwv9cCR04pvQiiWBF5/279shLl2vzT72VejHQ8GWa9ktDdnrXwk8PBdbSlhOHCg1ZNumtQKdzdeyT34T+nZv6EadANDXO4KrlwReecFfVyF+5p0NHfoiWJ4v/s85G3pyvu++WUJoZQV8f7ck1uIXlkq9b9ZHJpUoRsE0nYd//fWX+xxgr5zvhsBiqzuXM4Tggs4UnFjnYmRsf7CKBhrhwfmPCWCNx2wXtjmCB4V1+luBnjNHQ++/Fnh7tX/dssCaJYEVzwXWLPYX/CnE1ZnjwXdWcylQ+Jf4eXx/EDjOmeWfOiWLg38WPOz//lDQgk72O6sC764O/Pad+Fnwe2jTisCOLeL/pvrs99cGuLrT+glAlzwthBZalSEk2pCGPfzo4zSSphr7UxSjYJqNhykFVqGTeciGwCpMHBUa1ABnf0+kv2IEsMy+Zqkapsy4JxlYF34VcPn5WPCHAwib4PbNgbUv+l9fGnjjxUDVRXHp8O6QBNavJwQm/jgdeuox/9w5QpNy8M/Tj/n//FlCUJTkOG5JweqL2YD16Bfi/yuXrEurglvWRDXv+jUBbHlGkejTuyf4aNi0mbOl6jfrI8xRjAJJNh6a5T6lYocBUz9WaZlLYNmYwk/FL/wxZkyXqiGQPVkBK3uiGOItmO8rK0g0sBqEOtu4PLBppb+xRsDl4I7Q5pXBTasCp46In0XnQv9cwLjPh/XNyJEx4zML/MUXxKVTh0ObVwXeXhU8YoGpoSb08brAqYPiUk0plQQ4Nq0MIK448/5bmGvCwYGZhVdWzueRAsasjzBnuIDlKDPKOLuhRDfeDeI5fyTRnzFqNyVZz7isgLSx8GBhY4GMxx70798VLDof+u5AaO/20LnTIam2PnkL0RLEuhLA2hkCVW+tBFgCLhTGJMf7gM3OgXOBnyU5EljBTRZ6JLAaa7I/Xhf8ydKSAlirAmhGDgmsDzYFnnhYtAFflwTWOF9QttO4m39qZONhSoEFstIArNJEYJmoQp2k0FIcN7axIAUsjHecT8LufkDIm5XPCtNq86rge68G6ivFWz/+ZWjrq8Fr1eL/Q58HEULA5aejluX0R+i5hX4QOW+OOPAp8BMxxqWfDguxJCTWLlGy/nL2h2vRsBawyrJFJasE7CSw3l0XoJKZM4Xn4u4YsGgk8sbYR6oYBdN0Hjo13jNRYsntCN1IrL8SSQ4MJRkb79SDSRtVheOEyxsFxGgfj+jKF/zrV/nfWCaUFAbQ5WJL9hwKvbsmyFCR/0/sDb67OojdffYH8fPXk8HFi4TBztiQA0N+8UK/vHT2ZMgy3oPf7Rc/K/Kzt7wS2P+JABYYxcbCrn9/bdTGWrNMuMqw0vD+SxsLVShNb2MnsGKUHAkqAlspBVZ+QWHKgWXbYsokZf7cOc+BJQfhxHoVsJATRFeeesIv5ceOrYENLwc+ejN4s97yFHwSXL888NdpgYn830NvrxKYk7b8wd2oQh/OgjXL/S8t8WOloQoPfSkuXS7JphgAQqoJM/9UaN1LgffeCLZaj9i5VYwukX9CS9Zm/3MB7njflKlZwt1gjQox3mkkbjzPgQVLUwqsdIwKbdM8DNwNYs0jjRiBq2EzwU5jPxbVPvzoEzFVyCA/i+AMHvMz34s3ffNKdv4fIZSXGM2VZq953r/i2cB7GwL4rjhTlhe6XBKy7PHslUv8jArXrgpIa+z5Z/yMClc9H8CisrAVorBwKFzL3rIu+NIzgZf/FThnjRkpgMa8aXlKj+0PPvaQcKQxhsCDL/1Yjz7xpPSbm/UR5ihGwTSdh04dpE6B5WizTENg2daWMFlBICfHBizlQZY7VhoQYSKqXbpsueV5F36jLCukI3yk8/0nDgWlZxzplftH6JVlgWee8P/jcWF+bd8SwFEuldelotCm14OLHkGB+pY+G0Dq1JSHFj7m5+fC+f63Xg9QQPnWt20J/vMJIkICdkv/5f/9dKjZwigS8YfDQemwmCZCQ0J80iQatmz5ChpJU836CHMUo2zAotqU+rHS4Xnv7e3G885ia6T7WZ53x8tuk1F0XiOc0YpfJIabfc3ESYj5v/3uFpUzw8AQMwv/JCM7hnjAaPmSwJKnUW0i5Iw34YH7/A/NFUY6vi4uLXvWv/ARMCRC0ffNFq4KkHHfvb7Zs0QNxJhxSlGAYhTmFlHyfoEeGX7GFHvuKWHPPbNAhCClG0wGs/8eixW+8+5WGsm8U7M+styIYhRM03nodJMBp8BKR6yQTeoTY4WOl9cl6eqCRnowlYwaM6aT9w1zjx47JpOf0IZ3W5lYIjFhikiYASu8bDQjXlNGi8gSjunTfbPVpTkCQJTEMsPhTloEUUJ5yNQaLs2JlbzPKsnJqZOzpk0RXlCi3XPv9VP//bOF94uToYmiAXfHAoUc3544QSNpqlkfbelGOg+ZFpbSWKGjrDvjCauRxOwGx0vZylXqdGCphDXSP4y90kT40TIhK9v9b9FUKp+VSoVPK2vyJNKkBErwyONY8lvJevISGpOQs8ivmoRJJALP/ujhUwclZd6VLMktIZEzmGXlCQqHAraUeIS4JOrkdpXUJcUVafgyY904ugBzFKMws3QeMu07pdkNjrYyNJ8J3d5WovKxwmHHi63p6R8QXpmfYwTfjZ2HAIsP94VlL8Xzia3kTwx51KKVLyr+yrRPkaBs/R1v5fGJSz7xDyXHW7fYDyuJVJQUVVklY1nOMiNZPUJWwiWVhirbg4FF82ikcQdhjmIUTNN56DTRz2k+lqM1Z8yBVX/tmpsd7gim5miE7an4xYfYZkp80FhaBw5+Y0tXjyJsfDx1/W/aoS5Fr45LuHr7JVXN+iW9JYcOH6F5SGvjDsIcxSjsKp2HTlOTnWaQpiPnXUis9nY3Oe9YjnmJpE8NMPY4cCNvrqi4eMbsOZk2S4cmycUX+KiMe6dYRK6sjYFOx+ZOc94dLb9mDizd9GN5X4MabHzRJ+2gKI2/abnuwwcffZxpwGKKBw2jecZdgy3656dzz+m8QpEdXlHhcDOBvnQAiy2gtXmFJlEdhtz5GjF+VtMv5cpYZoT4ZP0MBMNkawZYhhw43ItLSuTKl8Zdgy2KRehEnXtA1rEr0cm8QkcR6H6Xi9uq3eQsj4PjrYsZOeuswWJQKxEwSGxzQUgFZrN8tuPzzAHW51/sokluPhhUISNBxSLYpXPP6RR7pzOhHbnd3QIr0lut1m7o7XU8UYcVVAoSSV9BhRxc43eAF5G3iI38xMKnMgFVjy1YxKiCJjGV2bhTZLvbFhLTyemiIE7X5U7TajMxM6tdrTbjaDajPlenUCN92Se8Mq0uCKUDsH777azfybYAqTj82ZMJsNMYmuSmR3IPAUnYozrfnM7PgZgK6mxre4fv1xWw9JiA2fpYWAY6g0j80FdAdKMNGXlROWs0fvnV3uEF1q7de2gGqOLbc9Mj9KBiDozS+UblTjnP+3IErHB6VvSLO9Bj8//NlplHNRQmElxTy7Zy1c0nzsoivFFWaFm3fuNwoWr9xk00gGbQGDd9gRWKLbAIEaUzzelSkf3OVwRxur+cW2A1t7TE1yCNmGy6Al+KNEJQ6esBt7ojCSzo5ZWr04+qF5evkE+nGS47AmcUWxjZ6BwzWCfS6Rqkec59GW6BxVTV+KrJvVcNasCkLdYID7K+QL5L9QFhqzHtmM0vlq9YlU5ULV+5iofyaGwjl10gsAGeFE9IjtA55nRmfb/zVZPTus67glZvd2FsnXeTve34HIsTCeNUbemB9dDmmsAuo3HeMYopPaja8NZmHsdDebT79svtFCWhB23sMthAoLqmNnN3poiPDbuvuNmZol+sXVOkL5CPZar2IZJCq9U18VnXWUQYUS4ZkqKDjPu9X+/jQXjJGQm6bzndhwmKIQRVdV45TZWJpu863JkirXvpxL1ZkYjxauCS+LgvJhLfpWKle1UiSSomUgDwIj/5VEr2E1j49D9zcnN5BLIK08qTZiPzFCtQiDZG1TgfjHc69GDlpHn3L51Ky6I7pPOdGuWj9iLS9Y2ZmBqgtqmFoThLW70gOU4kqMK7/+rrfXLahSfH9Hvu3bf/QL1FPIL6PWkwHaf7ihWwRecSTDNICqeFKXWNegksdLDcr/BafR5Gl0ENiJMyjeCavgsyyUYtHhGvChGIngJkfAZf7N5zz5z73UBq1n1z8VTVWRVSrdwM1qvWErdRTIAhiCidS6yVZcBqp44GJnkOG7D6+npvtZtnk0p9auMaYNK31/ZKBii3EAYQfwn+8Pen02dWv/Kao61WKbz61ddPn/lZVSIr9LCRdFnnACDT+YOB5dS3ZKAHnS6W7DGw+rXNwMz2hIbwC5QlEsPD4zEiOoYZ2+Ip8eYkGq5bBD6YQvXZzs9fXfvGon88gygik3i8L8iRPWU6PznJpR07v0A8U1jeJSFFVd62jc7aum9jDgrXgMlOHe5l5eVmb9MzYOHQUrvYG3xJ0tJCaOnbazPkgafHYkRgsSUFRHC30iKwcvO2icLyLg8Vn0741lXHYQKs0DkDowxGaga72Bs4GjwGlrSTzAKWeiJ8RSKRdXRUI4bxLSkj/JAMsngoshPzRdphUixJ+4mTXKIAxSicupYwqNR7DRNsbLliNEhqdpiLbDZj1HtgqUwMR5P87QNMKytBEV8nRqtiMd9uiiRE5hAdxKJSXab7csd1RQYhZ0kFhUXOcrBKy4zfo5fAglhN8EIOMxvRhk1mNcgJETofsVLh7+EYkTyJtGgepUTXMKdUZ9GDKEGbuHI6acIsAYuDyE+mAKu7uzUs91ztKTFIVlZWfGUiEUA8rBHrYYxWccWMLr2ndNzGCqeTB+OGisPtenGMGutB74ElQoc9xbGdfA2FFv1h1HM5kch60DkuPVujTFwRXdb7SAKWjQmwxWxg1O9w/Q9jX3fqgMUci6aOdrGmg/G31S/mUnZwe5VGsJU4j853OY9q1BAqT+8d0Ru6rHMAhpgpQbPxYHd3d2YBS8/+YxK3cSV4hmycRREQ3/hGI8bko0NukVOl94tuJn9XTic6u3G4FxWXuMRASoDF/mOxhW8uuQJoRUVVImHXMzDU3wE6caRb6xhSeo/oIN20dbyiosLlSyFA4CCM096eicASHqmrVwsKL5BO0xdpdJM3AYurEwk7g0HiQY3kWm0jlGi83he6JoOAti4bm1a6NrQtoj54vmiBewCkClh9fT09XfmWFU9Wa49xPShTsFWTSJxhHH5AIywS3D8jC1I0mKCN3osjR44kdxZZ5dLciX+ojIrKyz2fkJNWYFnypgFg9XTnhjvr3dTDWhTS2a0T9hZbLOtvhc8dlTEiXFw0Et86FrrefpyiMotaJ84w+9Lb9zL0WiAuvQzpABZi61ZHaXGJaKtLSS6nRdiYjj1Lqu7+RGIVA89j1Z5Hl3Er2JotrfXkj8d4GDg0EWccTC0aLByUfmAJI0lmwRJgdvudNTWBpNpEkvvI7UsktCSXMlN0kWSHoLU1GH+VTJDXic569Y4HM7nwVHmS0zcMwJI5C442qR462gO7ryQRWQ+okq8TiTlSGWV10Ri86rZGor4Rsck9oputpmkFTsfvjBbJpWG1Mze+oWEAlreEwYHKSH4TaA1sFNtrQ8UQZxx20UUDaAaNsTUPySrzFGyUCrsq/XTXiGsxQyRYfzWJyKhBJPD+9iYSugZH0bBILyCFP50G2JpEI2kqDU7uhYdjwDFgmZhucsJCMqFEGMN/NRDhSiWtqiktxIN43IDNIA1msMbzwbj3V92hwCJ549ChQ56wT86yqh+IiJDwlAHfK4N8FBO5e6kQUSSjUjmPGOzRNGzABsuO9I8iSjewsBAZFnn1XRKrltO5kgnY8RZxZH81CGE4M/kTvzZa0qV8wjDCBh8MTxCeT9QxTRqwqalzK4wqYJGOLVdEljunyY1P+V+aDnhQ5AYKiC7sD0qSXMWwztgpJ9WiTCMekCS89gxJDCoZ89MMXKxMjkDwDIg2TnKJAhSTy2Jz49A1Y6HTgMHaJgeAHqo/kkthKX4yGC5354P5zCxXIz4G6bBd7tIgz9A8ubYZr4YbvRo3eA+s9evXb9q0iXf58ccfr1u37v3330dE8ffDDz+Urs7nn3/eiok2LFu27KOPPuIqtxB5dfNQmIiBcm1w4iprnIKD3WkhzHNgR3xm6CZ57lOA8xs3bkRAbtu2be3atf/+978ZLH/wwQfvvfeeRNXWrVtpHglIa9askcvUEA2jAP/gbeYfT9zuqQKWbDFfwAsvvCDD8oh68ETHdGC9+OKLculwyr/99tteWV0NgxOvUy46wotPEZ5ILMbM4kFDNEOu7JAKBQSwkDpSkMNeueItQmjJkiUILbmloywJE3bt2iVLbt68mZ+rVq1CJGeuKgRYKsdj+fLl8FGCbPHixag/HVgvv/yyLEZ57vLKH4vZBLwa/xOhQLGx+Eyxgb5wQdxOJVQlJ5ANTXJNB/dxiCGApZZPBigSvgghmC8zYfiu0BLvvPPOq6+++tlnn8mSFAN5TCbIaBtreIGl4CUDPo23R4gQrBPsQiwtwnbocfKiED+ARooi/uEnJ7lEAYpRmFt4Jbf5CAkpg8mAHgILnqMB5fJ/mFkKWHSHwp9++ukYsG47HFZXhwCDlTeGiXg0DaAZXtkuboDFJQwv3gUf3icWcZVGrly5EuhjnBlspXknAks37ZEWvN10Qkp6a9MT8rtNYIEnjHpghNxCXG3YsIFLjKvw2VIMFnHJqwaPSM+7MUnTChUpHQqeE9XKfB4elLFMYMCUBv/+nQUsPQAgJQqmD9rKDZi4XYKJCsNG686NSrprjAWoA7kDj5wMIweVoESuTyRdo3KhIunOoICcPiR3xUmP8TQGrDEaozFgjdEYsMZoDFgeE6YMkVTMGn1cc9Yi47GxHIG/8sorI+ttkeBPs4n0jQHLA8JHDDfJcpE/GSoTz+YM4RRjw3kYgYVviSCxwT4lY8BKLbAIrfCTuKmbFN5hBBaxIB5NdGgMWBkELPn/a6+9pjLjcAHAaDxJRO54W+Q/2VIx5drDJJ6Tx6KSjRSwcCIQ+6PyIba+BcGoXQBNho9tI1M8oritmRlBdoCul4m+0VSc3bSNG8mIksIVVCFoefSWLVu4RECCPAj+QdETTqGkbCGZCKSpkeRDaBK/xhiwUgssljAgHPTSSy/pOSfkgXD1jTfe4C9X+bt06VK1zRr3qpP8JaQvL0lgkVhCbeT2LLYIcCQ/HXC8+eabsgZSAKgNkMlLrHUjq5V/iVCphddXr15N5QBXPl19GAR6yUKTt1AehypZLvJTkcXkjFby2FS1kAy5jAErJcDavn37ihUr+IevX78qgfX666/z6QMXhAc/EUJcAkOARoolLDMypbhE7psCFkCR6azEecABL1Lmh+nEpBpKIjmoAT87tVESiYJoARngEmFDbYgiagPfUjIBLIlUZBISlJopqYSWrgolsAiqIuRIDKQ8UTzOkIrDE+kCXaNmKSnHgOU9sBSBDN1ml8Dixcuf5BvxkyyXfis3Un8N3MWLR4QAi2QbS2oohKLt6bxXYKHsORQWUMMXzz+UR8OqkqRfcgZ8S2DJWLskQuxc6rC2eBgQWChB+ZO28TEQJFbhPBJfJc7GgJUqYCGHCMjzD7JnMGBhKvGTZHMlbPT9khF7nEFhJQMLOyxZHPJ2wQd6KrlVqCfK86bVGZQdZ6QItAGLjBQuSbtwQGCpoQnClZ8yk1gSqR+c+fzzz8eAlRJg7dy5U47V+aARIcrMGgJYnOR/5vepqkgRkUIlGVhMF1Ow0AklRTJJcqukINHfMQ/lDHnJboCFVOOnnqjN3Aclg8eAlUJ3A2zlJ6JLZmMOASwgpf6X7wzzCEONtCQJLIwkldLJu+RM8nKMZPFi4sisMglWPB3EpDGtKA9S5XnqlKa6zAAeAlg23WcDFoQe5MvBipc/0bYUkCOGMWClEFhgAk2hPvohgIVhxPAKWKATMY1JguMShpcy3iFmrfC2GKxJF0Dy0yU6qYdizBwELmAaFUkN3MulHTt20EIpCxk6qFHhYMBioCCHgeADiyoZWBI9mGX8g8eB9lNYzt8aA1YKgQUxOkNDwXFgNASwZEmMfUpK+YQJLA1/CSxkD5CSHgHe/WB7wQNKOSClHix0tboQIpARnLwdWQjQ1SyJIYDFo5kYp5wLycCiANChtbIMkMVbNuZuyETCiQAaBpsbgzBo/k+LI0pfQ8dAO/fhi+KS01kSeFOTXRvJTxy6TCbT/wNm2ze8b6ZK2QAAAABJRU5ErkJggg==", + "description": "Sends the command to device with specified value each time user changes the value. Uses 'setValue' and 'getValue' RPC calls by default. The name of the RPC calls is configurable in advanced settings.", "descriptor": { "type": "rpc", "sizeX": 5, @@ -56,6 +63,8 @@ { "alias": "switch_control", "name": "Switch Control", + "image": "data:image/png;base64,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", + "description": "Allows to send the RPC call to device when user toggle the switch. Advanced widget settings allow you to configure how to fetch the initial value of the switch.", "descriptor": { "type": "rpc", "sizeX": 4, @@ -72,6 +81,8 @@ { "alias": "round_switch", "name": "Round switch", + "image": "data:image/png;base64,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", + "description": "Allows to send the RPC call to device when user toggle the switch. Advanced widget settings allow you to configure how to fetch the initial value of the switch.", "descriptor": { "type": "rpc", "sizeX": 2.5, @@ -88,6 +99,8 @@ { "alias": "led_indicator", "name": "Led indicator", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAABCoklEQVR42u19aZBkV3XmPS8zq6qr90ZILQkEaEXsm0ACg9hNBCZmBsZAYMcYRxA22GGH/csRjvAP//QPhyPswI7xTBgCM8yAPfaETdh4AAMDQgIEQmKxQGhF+95bdVVl5jtz7j3rfZlZ3S1R1ZXtTpWqX77KfPnyvu+d5TsbIGI6+zj7+Fk/mrNLcPZxFlhnH3Pz6J/B3+2GG25YXV196Utfun///sm/Hj16dOfOnQDwFI58/fXXr62tvexlL9u3bx89PX78+GAw6Pf7Z/G0TYH1wAMP/M3f/A1d7N/6rd96+kf70pe+9MQTTzzrWc+aBNYXv/jFf/mXf3n961//rne96ykc+V//9V8PHTp00UUXEbDonP/sz/5s9+7dv/d7v9c0ZzXAtgQWiYF77733qUmRU3qMx2P7/TQfbduSA0S/n/6hvlEez3/+89/+9refBdZcPujKXX311SRmnv6hLrzwwj/4gz8gVfj0xdXhw4fpvjr33HPPSqzT8LjvvvtuuummJ598ktTQK1/5yvPPP9/+RBYVWT+km0j3vfzlL9/gILfeeusdd9xBuuxFL3oRa0YSlm984xu///3v33777YuLi7T/8ssvj0f++te//uCDD9KRX/3qV8dD0ZnQnxYWFt761rfazn8rDzrmOeecc9VVV7Epxg/Szt/+9rcffvjhpaWl5zznOXSehMjhcPj5z3/+rrvu4i/4T//0T/TVzjvvPHq6srJy44033n///bT9vOc9j/abMUf76TgveclL6I333HPPW97yFn7LWWCd8uOrX/3qZz/7WePevva1r33gAx+gleVr/9GPfvShhx6yP21wHELVl7/85de85jUMrOuuu45s+R/96EcESjP8f+mXfokMf7bN6ch0Cc1yjzr0yJEjdKjl5WUGFp3bpz/96e985zv2gq985Ssf/OAHL730Utqmj/jEJz5BMOr1enQQ+hQ62oc//OHRaEQH4dc/VB4EekIJgexjH/sYSTL+Ex2WQPxrv/Zr5HbQ0+9973sE35tvvpnASk8Ni2eBdWqPn/70p4QqUjoEpuc+97m0rP+7PMgoIYFBUoeux969e9///veTnCBj5Qtf+MIpHZ+O/Lu/+7s7duygT7nlllsI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", + "description": "Visualize the state of the device. Fetches the value from device using RPC or using attribute subscription.", "descriptor": { "type": "rpc", "sizeX": 2.5, @@ -104,6 +117,8 @@ { "alias": "rpcbutton", "name": "RPC Button", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAN8SURBVHja7dvbSxRRHAfw/X9+bhcvrJZSmllhZFSCWBDaY4FUhBRZCQXR7akLyoJYlqxU0g0tukF0oYQM3bxsYVRgbuuyu7k7szNn9vSb2Wx9qB56ac72/T7MHGYZmA/nMjvDbzzSTkbpOASPw7CUjkPx2AwhTNNQNKYphE1hiCXMtK5pWkrB8GXraZMl0sMOQ9dEdqApmIzQdIMlDDH1lLIMh5LSTYZYIp1MS6XDAGF5uEPiltoQK85dwhAtKhVPVLMhRj5ADAcyqzpkNgtJqQ9JAQIIIIAAAggggAACCCB/8wog8btfAjd+f9Y3t0GmW5bS8rO/fvavaXB2jWVlZbVHwjLC+7L1x2P8rN1ZQcWtX90EyWws7r7bSuf+BKnz+f1t3norTPV+/0HvNiFP0+5b54sahYsgn+mQrdksZfL6hYcZGe2betoxyG/LtP7OiXlIJW/a6F2Y9nPjKI3MLtrBjbM05CJIsqT6tdOI1BbWFuyRQVpbupw7yGygqqLCBZCTNJqFdNGTAQpwY2bwo5vmyL1iqr+uS3l4yaTsoaEg7bT0VXWyn/xyquQHpDz4JuCrFWHaG48OrSuJXqLHLly1Yl01VDkuq6r7+i5Qd5ABsqlctlMiN0eI0/BZhu09rXggu10J4TVoYMlWWeKr51x2IM3lcp83k4NUfAqVbrQYsn3w/lvuvTt0lQ9Hn027CNJLt3m7YaXctIaXYCHnIWfog5Src3Okg/pldo7Ys8O7S9qHXrgIEllWee35uYJ9MkAHXgVq3s9Dxguan56iHCRZUa3/hPDAa3/U7dtiuGloTTZ7aXFrnO9xPio8Zs5D5JViaqpbsGpdpK4cxDxRRItbpl02R1Jfsje2zIy+8LCI/ekkc0a4b7Lj3y8ggAACCCCAAAIIIIAA8v9C8qaoRosqXngmsmVOXHgWVxsSyxaemXosJFR2mBMxpxRQpOPB0YihKsOIjAbjdnEml8vOhUaGX97s7VEwvTdfDo+E5kyn7pe7JDQWVDZjIbtDMk5JuZGcSyQScQXDlz2XNJyS8vwp8s+bzy6kzJsPYfIigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg/yTfATinNXQTM7TQAAAAAElFTkSuQmCC", + "description": "Allows to send RPC command when user press the button.", "descriptor": { "type": "rpc", "sizeX": 4, @@ -120,6 +135,8 @@ { "alias": "update_attributes", "name": "Update device attribute", + "image": "data:image/png;base64,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", + "description": "Allows to send shared attribute update when user press the button.", "descriptor": { "type": "rpc", "sizeX": 4, @@ -134,4 +151,4 @@ } } ] -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/date.json b/application/src/main/data/json/system/widget_bundles/date.json index da05b4e53c..b137f66c9d 100644 --- a/application/src/main/data/json/system/widget_bundles/date.json +++ b/application/src/main/data/json/system/widget_bundles/date.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "date", "title": "Date", - "image": null + "image": "data:image/png;base64,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", + "description": "Contains widgets to change the data range for other widgets on the dashboard." }, "widgetTypes": [ { "alias": "date_range_navigator", "name": "Date-range-navigator", + "image": "data:image/png;base64,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", + "description": "Allows to change the data range for other widgets on the dashboard.", "descriptor": { "type": "static", "sizeX": 5, diff --git a/application/src/main/data/json/system/widget_bundles/digital_gauges.json b/application/src/main/data/json/system/widget_bundles/digital_gauges.json index 116b5c9c02..755ac603b0 100644 --- a/application/src/main/data/json/system/widget_bundles/digital_gauges.json +++ b/application/src/main/data/json/system/widget_bundles/digital_gauges.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "digital_gauges", "title": "Digital gauges", - "image": null + "image": "data:image/png;base64,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", + "description": "Display temperature, humidity, speed, and other latest values on various digital gauge widgets." }, "widgetTypes": [ { "alias": "gauge_justgage", - "name": "Gauge - justGage", + "name": "Gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 4, @@ -24,6 +27,8 @@ { "alias": "digital_thermometer", "name": "Digital thermometer", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display temperature. Allows to configure temperature range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 3, @@ -40,6 +45,8 @@ { "alias": "digital_speedometer", "name": "Digital speedometer", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display speed. Allows to configure speed range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 5, @@ -55,7 +62,9 @@ }, { "alias": "neon_gauge_justgage", - "name": "Neon gauge - justGage", + "name": "Neon gauge", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAlQklEQVR42u2dB5hcZfXGd2ZbQrLZdNKWJEAgEpIMiCCCIiAiiBLFIFhQsPxRggqKWFCkqKBiFyygomIX7IgCCqhgV2wRexAVrNh7/r87Z+57z/3undm+mex+57kPD/vNndnN3nfPeb/3lK9j69atW7ZsOeaYY/r6+jqiRRuFAaENGzZs3rwZUHWAqrlz58ZfSrSxsjlz5gCqDnxV/F1EG1vbuHFjR4yA0cbcZs2aFX8J0aJFixYtWrRo0aJFixYtWrRo0aJFixYtWrRo0aJFixYtWrRo0aJFixZtjKy7p2f5qlUHHH74hhNP9OtX3HLLp3/+8y/8/vc33HmnX+9ZurZ32bqeJWu6F+3m1ztnzKtOm1Xp6u2oVOJvdSrakhUr/Jcf/8EPvr11K9e3/vvfru7ubP2222z95rvv9veDqt6d9uLqWbJHtlqp2GK4zitdPfF3PpntoSec8Jqrrvrsr34FVpYsX67113/sYwYgrkUDA95j2eJX//a3HLAG1huAunfczaNHwOpesHOw3rN0TxY7Z8SmlUlhOy5bNmvOHH35vNe9TgA69GEP0/rpL3uZ1tfd+95av+STn7TFb/zrX3lg1RoAWriLFqu9MwSsrtlLsvUdZmfrc5Zln1LtrHR2x2e0PdninXZ64nOf+54vf/nW//3vuKc+VesPfMQjBKBN556r9YeddJLWD3M9Sxe86122yOdUHG3KPNO8FaUAgmxpvat/sda5JyNkfQsST7Zo985ZO8ZYuX3YfY88UkB53Uc/qvU5CxYAEVt/w8c/rvX1+++v+x/9tKdp/bmvfa3WO7u6ilzKeyADSgNAvTOyncGCXbRe6ewqXYfyx6fWdlatVg884ogTzzgjcxLd3Tf99rcGiC//5S+906bppau+8x1bv/6Xv9Ri3+zZAtBpF16o9VPOOUfr2YdUqhmw+hdn33T2Eq0T5twWcs8Go198D8fnqyJqPcvW+l0kDqw6PeJsW9tjTzvt6h//mAf/9X/+c878+Vo/+01vEibu88AHZjTr9a/X+vxFi7T+uV//2hYJf9mHP+MZunmHmTMF5Czk9S3IgDVvRQMoS/fM8NPZVerecFHZ+twBh7cuI3DsKzv7Fsbnu83s5e99r579Y57+dK3ve/DBWn/2q16V0ayNG7VOxNT6Wz/7WVu87Prr/S5SN2ebgGqn41LZLq974aqU0a/KADR9VinBAmQuDs508XRhRuDmr4jPd+Js2c4773yPLKas2WcfPfsrb73Vx8frfvELW//Y5s2lNOv/zjpL62ddfLEtomlp8aCjjtKH88aiE/IxCx9T9ECdsxaVEizdXHdvWRzsWbQ6+/CejKhVuqclumu08bC5CxeedsEFX/vHPy65+mq//o7Pf16Pf4973lPrZ7761Vof2GWXIs161Yc+pEU4uziZFmv3uY8+YeGSJSmwuktJujiTj4/d81cWCRYQcfFxabbes0PmrnZc5f+N3Qt3JUR2zVse949jbFAcUit6zHve6156CYFA689/wxtK93rHn3JKkWZ96ic/0SI8TDfP7O+3xZWrV2sRCaMohFa6e4vxsTqtz3mmNSVuzO8fnVvintK46QGH6A8Ri3gYS3vGS1+qx/zaj3zECY2dn/zRjxq5lz/+cdoOO6SyQOWan/60KC6IZhET+9P5TQju+vBd9thDPrLo87y/gWsrVLmQ112CNgcUCQ3ALrdPzDJFa3x8RLIvlV6jjdDusffeex94oL6c0denvRuY8FEP2i4EHPmoR2md0GmLX/nrX6UXeJq13yGHCIW3/OlPtrj/YYc1XEtXl+5csfvuRQxJJsBLNRYH1rutX18JwXJCg0cJ+4DSzWalZ3qWgkSYcEJGtXcmziziZBjWO336U1/0IhIpn/jhD70KdfymTQLQRe9/v8fcF//wB1u/9Nprtb66VtP9FC9o/cPf/a4tPv5Zz9Lie7/yFVs8+vGP1yLpZ1vcdc2aEFhEpQwW8xrPftHujrkvLBIsv0+sdE8vbiqTj3XoEUtLADdzvvNwlZ7F7ABqiYoRg+NQDOfxvq9+VYB48vOfnz2qri5hgkqEXffMFCPl+/Axnqp/9Pvft/XnvOY1WoSK2eKFV1yhxZe+853F76hgCkYDF+IDWWd/Y/cHv3aYWFEkWBIa8nS+pzTYee8IjPI6arbf9GiO1soe/OhHC1hUFixduVIvHbJhg17yYubCpUvxcLaOt9M6/9/QEW67TYuHH3usLQI7LYInW0R60OL7v/Y1W0TXCKi0f5zi3ejmGXNfvEdKsOYUhQawmL09yyfW/L5P0KxLG/05IKbxNPj8aKH19OZ0mos+8AEB6NVXXulfuvS66xpO6z//Wb5bVrhy4bvfbetU56Fj2SLcSJ+z0667ipUbecLtSVJ/wMMf3tgZfPjD+sy3fOYztsgeM2U2M1IhdNciv85Iusv8iGDliH/3tOLm0Yui9Ztr5eqDo/O+RMccfsRSZkc95jEEHWlFCcWeP5+6TWHifg9+cEbq99oLQNj6eW99q9ah86VpnA984xu2+KhTTy3SrL0OOMBW0F1tBbKl217xvvfZ4t73va8ocxEEeK8ALrrNhzwJDd7b+fSOJ+NE1dJ1vyeAznvdlf8Ho50z50VEdcDNYT/28MiroB2URj0EBc/i4UaqlPKB8vIbb7T1l73nPVo86cwzbZH6qiLNEtpIXVsw9fnpF1xySbB/1HP1zKmRaR6oyWEIQ/426reKEVPxzrulerCrpbxtRY6zp8E0UDEaOqqo3hRn9PsdeqjcDxcg8K/6VKDPwFDBB/eydZ691infazCzv/9dGhXapgU+3iKVSzTL+zySPxYfVSTztBe/OHCBcjBds1XaULGY5Z1T17ydQgLkhIaMSKF1pQDKJRPn7iT9wrMu7RKKyUTAmumoeZ12ippYM9c3//1vsRls9rx5VklcZPHUt9g6dQ2SxaFWKBQNtX3TJt38ri9+0RYpsAlo1ge/+U3dRuGy3QZwbQU9IshYSy9ATUgDUE/6pFe64Lg60Ev1Rl/QnAVHJ4omWSO5K79JdJydhg7Js40tRfqWwCNOLfM9C+gL3jNRBjPTDXO+/0Meopd48FqniOrG3/ymKCUQ2mzxQ9/6VlFBpXZPix/53vcsmCrIStxft99+tqLi0oOPPjrFR39QJio6n4GAYsCBgg9LhQavfwp/uQocSRJ5RdRz9lwQrFR9fPT4nlp0nszdu7/0pekzZnhF1GtXUGZ//0ve8Y5SFq/MMYFPxJ9gpyo/aL4tUnrFFtJQq7erokFSgupk2CEGPE8lyypB1v5fK6qikSSR42Hps5cPy5KALgNYd1cl+excCZdTyzpcKVh9W7A6x64qVRzklCjtEpWmN8ZTddpmbrzrLgHo6Mc9Ti+RFf7Mli1FFo/bU+fWM1/+8mImx/sniRS0ENrKgx75SFt5xJOfbCtr9903YPT3vN/9bOWI445LYTQnLW2YGYQz7d2QyAOCRbq6KFLIM/l4R71D1kMmZ+M4e1Jp49yYzwUB0KC6RsL9JE8y+koELipb/Kts6SFMKl9Reg4jf5fVUb3gBVqX+MT9qiBFLLXP+fzvficUgp6gHlA0S/SfEGwrxERbQdy3dyGIBA9SyoKKkvW8pZfKOamCL9MClHUeqMHS0rUuuatc54Xj7L4ILNG60tQ1GwhfM9iRLycsbiEnW7pGAahY9umjGxeqNz3KeumcSy8tZfGSGE49/3wtqsFGnoYSUEPbGz/1qYBm0T+oFSsPJPgKo/Y5apIGGQFoTHDyFckma3mC1RACwFDKuOX5fFwTRsOOReWt5+7kf5u0YpemrjvyxajhGycrts5+85uFHvb2vsUPe9Fb3qJXn/WKV2QBcdasa372syKLJ36ZmyFhrAJiKiPszjddc43uvPgTnzBCJtHBZCpWtJkgh51ksq+7TjJbI1w+6UlBmBOVsfyxi3EVw0FGsFKhwavk0pwy8RPpIXU/+T6fnbOdowuCCppF1lXfYdSavTp5jIDl+/KgVlI4zQN5iQEvJScEYqgP1kv3fsADFBB9xTpyqC2e/MIXavFtn/ucAVdNzw957GODHQD+rFG8kOazranVVzObYPbIpzwlByxXHmPsR70S1CwEBMttJOc6haIWFMgrY+gFqlzVvJOmPJdP3Jv79dalh/X5TWJuYzhJYuKm887jwdA2o/ydYUvZEi6EAyXybBOninUY/YLFi4uKuWfxzGKgZNlI1Yz0MDMEggBtvGQooYg0oFnsB7Vj5csv/fnPQXBUv2Gj0dQFPnuKqmnJSJhipVEusCgSlmIoSy3LXREuRcAdZ/dNPknqRl1lS/f0HdXEzWRmSS6TWCkSL9+4tl0ajchCD14qa/useyY/NIFCg353Hiet7oYVC2cCJSKFJFDP4tkV2uITnvMcxVyrmSELqbe/8oMfDIqS7R518pC6aRQop1oaPRp8ecLpp6cC98I6f1qdASLfbGMRyhMsA0dOPq2v+Nkh0s27Zi8tcvYkCDoRIWt2Haj54mZ+GL5vhiqic07EqmSCfsLJtlsNAuJyxc03e6pOAUKALc1H4CIC+hoH333lK/KQACwL5Fk8eqnJVzg/FSyY+0kyfYceaivqsteW02jW22+4ocHWlyyxG9QLZCFVuSZDgPJ6KpmS2+AlT7B0gwKQso1Z4R4K57K1QZWf5+ySNoLUTS7fDJdPeVuKKp8urPginGIA3c6MaOVVdZOvfDoZHq2ePi5u9mxMTe7o41LDMRxMscRF/aV0sQZiqSq3+NZWFMrNnmaxKK9mxajwOfuSoh1f/WdeRO6ngRJXTWoRTQSrsTtzEmjjAbsV1ZrmOnxSzh528ijb40eM5Ksh6qCp5jDnKlEbIXJ7z1IDFFXbqcoAqV03EN0uv+mmbFzHeedlf51dXRI2IVUKT+BDdS9i94leWp9oRXpRn//0l7zE9n3aMFqlqHaL8DmjWRJObXqRFFqTOVQ8aPRIu3djVFIHVHGVObC6F8n2ZUnWeX0+/1M1wuQVUXHzeuFotZi6SVym+wv0Df7JOLhqHlUuF1RE5PbkpVRk4rN1llSxi/iSNa3XtXWVnPstWEe9MEv1wZKXjITZB3oWr0infi9Cm8lXGjtjbagQOP0AVtSAEG9fnv+2t/El82rsSyt6FtwpavCwsMAk3Jg6laAhExpqXtVMN5U1p50uKBQJZpzdB0H5JCiXz0BLWmukdJwkkaR0XHwszU8nm83twnvxJ44PIK/it4G2TaNbJmNUN93kk80A6Kpvf1uBz1fq7bZuHcJ6oIB3uAIHrzJY/yopIImrlmpU+Z5GhsjVvfCNb0zGgVxwgU86aecIwvyr5hv0eGzHl31Zf1UEqyE0JFGv4rXTzIElGFoTKKLaM5a38VBIk6v7m6ngyJbCAy5E1cD6YhFz4w+j/eVTX65OI43PNGM0mqqFi4vEM4UxepXNvwIczVi7r1/vlTALWAgBKkeG6RsWYfG03jc82X772Z2SNMGlraghwjRYdbceefzxfPnmT3/a6xTSYOknI3Wd7Qr7FuJOqjPmeCYk1cAepJ6fwU6PTRIXHRlhVjFVRJNgWo+Vyb4yhaNP3XhwJDsDN7cyN8mt2glYM09GmU3QKwYGHS3zM73aznjSEsftotrJp18wvrRESiNv8/Wv+ykxpFDUfXrdHXf4wY2quWOimrwR9QtW9ulbWAG0FTJoB8qoD19dwybRZAj7Ep3MF47y84AtX03fikF29YIq+QljS3rAFtEkbJqGlImicldOiUj5UC3DAbelpc+58qzO7kzNKqBKb2m8mk9OJ3KXmxORKhptvEnE63gFgYu4ox2W0nZ+JwjOfLU7YBKpwiGp4Z3AKt3LT7EyAdaHNrBrGphkTyvq4icxOSMZGXLHHV5TwJX6veootipVAcKEhmzsB/ioawpyb43o5hTRrDw1N3lrqZtMmT77alXISCiXqzJN5NPFq/0OMRcf6/X4XkQ1bWI7mFvJNhCqrjoFa6cJao55wMoTmzqKr9KrqPD24JMt5NVXy/EgndtID0Kb0jJ4L/yiuSiB44xXvpIVpFSrzOFHMjdJf71EMkot/OZ0HH4RVfi40jgNXp+JotZx6tSEdNNXr6kKe6yTDUHGyrONXhLjuntzbsxppEm9fN4PJcHX1ZomLa/trMJXCl4UrmMj0XRRXe71z0CGwEuJJ3XUe2bUpUM6SOsImwxoME0B72iLsDHD8VPOPttWEPFtiIiqG449+WTbUmwzudhYfyqKwtICRbTB2QeyIJilbii0cuiRgF5H1bRcgHOlpKGsUKmoEj8Lke3cpI8OSYmLyuUyEWHWLJ9sTgpUbr7ZRz2MfnY1mtIPKDEJW7V2rWpBlajpqI8YNeWdmCtAk94JWPzjnvlMVtgN2OaUH9IP1to2f36AIN3Ym19RXkWcnRbnjG+lXT2+BitT3kGbg0UdVWvkh3L9+HVP5rl8YzKvFybS/HTSmtYm6oPydEyckhfxmRm2csIWHNmPvDYqbU7I/JBvnKdo2F7C2cj3YODM7s82a11dkHo/zRbviMtEp9DYhTZy8NCvgfVeEbXQVn+olVCOd8qTqrhCVHX3isgnskJ+iil3OswlNwTFW+YdXbnp0m3/O6JNlNaarE7hrrtQKYN7qD7wSUOY9cOf8AR/A2DCXdmrREAvNFBRY7jkXeoaxVFZqYxP9eCQUNg9iweXRaC3C7Y6u4SMhtxFEEzjmlI3fsOYNMEaPVq2zhds8a5csUNPjjgmMddV0YDd4nDAxFEt83S+tu1DJJOGdDSILp66F6jMo8CofAshzMn36kDe1bJMBPRTivY56CBTVllX8hjebWI9DF2lMqZt4qiChv023+yYO8k8Eyx+8R5BZ0RSNd+oq1nv5fjEG6WY4P785q7iUz0N1pVnw2wYg+S0cS8P3G1m4INtoI93XAihQWloR71Sj1Doyxm8RwEf7AE1Qs1X//FG80ZUvMzbsfEAaCc0gk8SJpVvOklL+6C5XRgBzhdONWqdaZ9PXQuPv8HKqZNxYS7pPEsF0kSMcKmOBDFOdufTilO+WckCaBolk01iW2V4cDm+T0viu6+yMqHLSoTtIvfinRPIUCkf9EjlLtiBD3qQqVNwKQn6+EvbD6L4d0wKS1mU64yot3ClqOrPRcYUVYm474tIu6d7UlUiU1U7/WRKlTy0xYzTIF0jJu6FdePjBz/0oYE2wbx/35AjeUnc3JIwRECfNMQFGp/zrWM2jQ1HuD2Fv+bSl7mQsgm5NZ/MSfQttR/m88pJn6NI1UBNHdvuvaGjSmJf6akFE++6UA2IViRJpIy3joy4Ln9ekondqgUFRmRsfNIa9dLKiMEfXaNaB4JW1HDGRRdpEfbWhlu/EW8Yc7J72jGh7KSFsAZ08GEz5hTVsqYyVdFRNYt9hFJ0NfaeE6zIW0WAEWoos+/TMmP8QRAZSSP60Gakyh8hgS5FIajfD1oLK1s/v9NE+rJNgB9LNClNKWovSiVbyGxv6BoJq9Xc3Cy2k3mZKsnkOPm0eeyr8O2U8/HN3ONuyAc+Y2Nz94NwJjFTbsk8E+UoQQzljZogygZTQ4sxhj5arzNeyvdGU1zFR7HohYlJZvWZlLUQVQkDa5yJktfce7NMzrJ14cGIuJ+8o2oW+4iwPiMUNneMt/HsqcAMeLqV72kOgoz8HbK4pg5ZaZ4UKSH1nV/4ggpmNIGjoz7k2DQwvBQjOrSOsO7LsCanx+pf5Fse6gV9tWJeOUldq3imIFOFjgp1lCmVhdhH0LRq/ZDRQ/y7p03oP5vyJpiTGv3kk1j0Wb/SyMhtBEHvurzQZUk9pWtg5SaHsh50Tk8dU1hMYpyDRT3DUyuXqUBfXscqjX2wqOIO0SCVawGaYCOjB2J82bExbni9J0zaM2qMsSmZtNwEN6iugcZDIc9nrP1U46mCKs0H9HllV6kHJQpCGxKXD2rlsc8YutPlM/y1SXKaTRnw8rkdax+F1wcqADTf7xlxUUDQ30Ps4yAJzbjydYIUhfItfDvhVAFW0hpUyx0sQHlMWsoXylQNR1Vrte+r5Bh6HlLTt8G/kP4+NCROLyp9larL89/+9gBesO+iBB9ERmoQ/PE4VtFlcii7Tr+XnMRsfVAun6NNhomBsJqK23xpaGnsSzKD+e1hegJUb5NvvQOvjmODKzoTGRWVq1O24LtPMya+fDlOyDIwWcHMLbf480vMSNQoMgJHeJWXLRjsYVtCXgrqBKeyqVIPbT2X0cMJwbfUWJHEvv7BGXpLSPEJygv5vu0xNn/WskgSrkU5YG9k9ACK3wwar/fHRigyqgmHWmTwpFfp0MIFFo8FmLKWNfDk5yInORwVuZfFPsaQFKTRGisazRW4EMJu6NXyZWFjaSc9+9mBfKWEMc13vvFBRmYQQOhEGlM72Q/6BoogMhZdF30yZGwmjbY+qoBYr5DJq+05R1WPfXn3g9oO5fIMHeTNWVYqqSf4o8Q+G+aWA+I4zswFEDSnqwHQXyCG0QxFHcveBbykgjbj9bQ8qJoZ2q5urY7CuRVTG1yVHM5SR5UALphPVGToLSBFlEzqZ2pFSLG7hGAFHRnjZZQkQKRUOuyvZvSLiAmYgJTupLiPO329vJ37ZfyM/3K/n1MaLdQgzFENlJwBBj3KxbJl65LWsWpnayKVu3jLvOXbZl48joSEDAfOBDKp0S9gUUxRQ5sgVZrezsUE5UCIR19Vp5cf3hct46ZpWrAY+wKGTiVWQrmKkKpUE38WJHBS+T7JI01AaQMldYze9wWfRfkAGGnPmNGvu+/GsWnAvwzlE3hpuppVwvjZaxYZ+cDtrmpvYoykYbIrzO/7UBZ8czNBsBRSidRer1wI8bR0T7xaq8xgpQJ/H8uaLZtfQOCjqOFe979/MJTB6xEoCNBwP6PB6Bf7waCHwraE9PZo1HaD1y9YEKnVcMlWwNCBVEK0SxKC0xPkDdQCVo7bq/Ozpp3QVB2yhbQa6LGk8H4UjA03AwHoUpUmTdkkc0CMDvvTxcQOQmdAm4AXTEuF8/QD4vzGpi95asAriWgqe+f0LwT6AqSgSsEwowYrn7VjC1aeFIfNXhJWBY7VuZvUHBcplAqLiXRF5VOGRoB2oLNJVN1AHAyKZ4izFFrpQAAoWukGM1pAp7KDBZbskVTLBH/qwG7G3JBIwcrnDuRquYp46l9c1LHcCYxjEQ0pWmfcFPm7IMD5i3JkupD9yP8ix4dF+Vy10a9A/SKYQq2YFIJkqqaJaE0RwIEDy9YljicpwMq337AtBxzL1gbjIVuwchqBSEqW0nmpFYw/TT6hOqZbdcITFAoP5M+9CS6yfugFAQ33zo8w51vvUVwT+uXGQBq8ohw6dMm0hEghsvtewpasHPdD9Az6pIOqwMTDQcImYJ9I0QEsCkxQl9cMYWAoaKgXboiesDRlcprRr3E1jizkJ2x9NfPBZitXrx70E4rtu+O4VQyIFKx8/sr6zrFS6u3wPU3xhHNauGvCwLq3EdmVG9PMNH9RFQNiYFSlHcmoXKDTZvC1oF+lNpR7WtzJIn8b195+ezPXayNx+Rto9o1Y59UXX355i08gE1WseRyuDf4vLRCpVqycv90Zc+v4q21L50SlOYc10DPjVYBmxi/R3FjQomNF6wCIV/14SBm1zgnHT0//GkocpDa6tTsxY5zpMU98YnGd+X28nRkkXqr1F6ov21XeHkwDkJE55VUek87FKKZQda7diI0fMjhkrzQaelZeXqNXn6mU4Gmg1tQ5DaG4D7DyOQj9YVn9cI1JeV5MJ34hDegA0qZubPr0Zm6MRA0sng/xw209x9fJW62N/SNA5CCdFvdYx31xDE7y9ttu4+0UgTFIkp8zuDadey7TaWz0iEZOlmp7p5xzDvjjWOHih/hO7pEZPxsN5XQDDHonFKruZioleJreX9eu1hec05qGc6pWBwcTH4Lu4KYE+nOBRmJ+ZGiQsRkiyJq5MSpqQBhIKraODRFY1uza7AfQQWLNgGUTTf3AN286lLU1sPxY+TFnsZZXHQqwSsAwrS/BUyCvD9k5lYIpL9OvHfm/DUy0IBDDBZncWNAqTdUDYj0qQ4uUUTNg+fOVArOp2q2B1SLsqg5sUGD5I8rG0Mhw2OcPC1ioU8SpoObYOafO0YApVLNGk9thW8cjR0HAuwQT+lqDrLQ8y1MH/tAhOjowx5rxNTU0Amu4wEr4u5fI2RLinCh36W6VFkPuGgaYlq1jF2kEa4x3i7TJoxQQJvAx8KdmivzQQSY3ZjXKOow5Amu4wLKjqYfinIYOJpAKl0JoTSZDT2SvPVs8j7OgCWy4IGNymo6ojMAadihkYHNzLzJEMOWQNDGVfUMxaDjPCfSQq0FcCPopPMgAIs+7WE4TOdYoOdYwwVRD8ap34CxIUocTM1uGzT8Pj5GeDFWnt52il2ID6iB74O5uj7PSVOOIQRaB1RRMdZ2pHEwDo0NStZO+IDr9ASuOzc9QHYbRJFjEATUtJAqBAhIRpJ5tY7U61B+OrCgg4PxcDoVjyoPvsLDLtxYOEVhI50Ffhoyk+KDAojqj2dvVeN0aWLBMej3GA1jki6zHaejASiaXltDtVcxCSul2ZZgwmknaB5iyDygtdhjJvBAm6A1Fa2BbB7tCpuJJIEpRCz/EqfzUcpGrJmGHIoqMTmf9sGqwABb1pUEzWfD5Bo5mwBrq25sDC07p55SMuZGbp+JoGB6rPrcNENT10jnDeeqV5OyWaX14Mrh/kkAsbdQpXMwjGfa/asOJJ7be9DW7KATloDbOjwQxxEH8UGkaZ5TG53MGDioiU7VwosUL9Zx8CzO0SnMyvJ2TCvG4TD0tfTuSOm9HZW2W0mHcDQlm3DBHIpZ+AoVGoyGR2tCQMhrj3121E9+GD0si2vyVySDdgdpQYFS8Rn60E/oCToiox989f8Hs7BC0+K1pMvsQL2IoNIu3K4ZWRnckEBSQTJxvKSteDDvlu5Q6wuTte+/d+l/Bv7TZ2+0TQN6l117b4hMo5vbjvkZmoy2mVURLidHw0FOfR48PA4KJgjVrRxCJb0vc4Tgd6gSXh/kyI5RENckyhtKSWKSImZS+H7vd7OKh0nfPOShAjYFYzbhO62BKJjgYFeELJRj0wB+Gn2Iavv3MM5tJJMyLI1XH2/c/7LAWPwN/dTpSKrj4weyAYB2xOTIb1ttNPefxc7oJR60OMaIRQCH7idAwdyAZymXQgZNV26/3rpmra+Zj/LC1YRk8iW9UvIi/5AHprQX0o3n7oFOTKP4p/QTzyuwZgfgI9YJKhVGr0MFhuCc7i6AdHM/EG0+CAIEnwNXx2Bgeiavzp+iMiZE1ssMTWwOrxabMDssY8Tgu1BabGzAyYPF2O756WMACKInj8dBpN8fDgEb+SUQr2DFnLVGVBZ9FZEcv4Eg3Jhnxq6d8r3TgTDuYDvYdGbB0QNCIgQXvVlAewduhGZrLui1+f5XkUJbuaQhX9BKC0WTD2L84kR7mraDynThrLnDYH9y6ujIosYJXITpAz4l3/JmiiBL+QCG7QqIhaR9+TcE47gisbQAs0j6d3XW4zEyomIMLqimuDqCgoIbHVbQiZ8NvTfBDFsbqQvGj+/nKW29FIGXDf+5ll/H8EIT8EeIRWKMEFrNoEvI+eynHVcCoEEg5cDXBSqHWbwwuzlscrpVOLBqnC7RFYI0VsOyApwm6Bmoj2dZN2FWcHRKBNfJQCFWfyGuqWeRY0cbFyLSMBljUHgbAQi81gYrjDobyCcrijwxYbLojsNrRiLBM3hoxsBgsYGcBC1gaukQvYXAzHUfF+I4Q9aTnPW/EwML41hFY7Wi0epLLGxmw9PZBgaXDhYPrsuuvJ59I7+FokMGpQdRxxEfZdkatc3D4xXDfrmmDpcCidqNYWKYLYZm0zAGHHz6af8Io3x6t3Y1DXMl7cu178MG2Qj0ZYY7CjRbXiP1ltGjRokWLFi1atGjRokWLFi1atGjRokWLFi1atGjtZlSDnHXxxUyJ8ReHN7Vtu040mR+jP5TnyD0vuOSS4B5WxuVZn3r++WRhGUyg78TEx/0OOWTElZbRJsYoxPAZ8aE8x+I9jG/hHurMxv7nA7BMuvIH3dAxzPkU4zQmKtqYGM+I8h76qof1HHFgzKX2hz8wv5h7qNseF2BRaeQPXKBmd/zmj0UbE1u1di3PKADWoM8RYLHCSB+tMA6DlQisaBFY0SKwGPHjZ6kxwYwDLCKw2tngUjyjAFiDPkeAFdzDsLsb7rxzXIB1/KZNfHqwyDAgugbi82tnC57RUJ4j94C24B4Kso89+eRx2V+Urg96qE60bW7+GQ3lOU7os8YNnnD66cEig2VwrfHJtbMxT5XQ1vo5co9/joTF0meNsjouwIK+0T+uFTqDWfE/dLR2syXLl/OMmKvY6jnW7/HPEWCxQk928KzHxYnYD8TuQCt2SFgEVjsbO7tSYPnnaPcUgcVuMXjWEVjRIrCiRWBxXjIzSDmHwi6a0Oklj8BqZ6MegbPTA2AN+hwBFpnp4j3jAqx9DjqIEwb9iUsMND/oqKM4VCg+v3Y2Tj3yz2goz5H/D+7h2BHu4b3j8iMecdxxNsVAF3MNKpNmXPiUsUGfI/9/5PHHx2cdLVq0aNGiRYsWLVq0aNGiRYsWLVq0aNGiRYs2Wa2vry/+EqKNrfVznNuGDRviLyLa2Nqxxx7bsXnz5jkTe6hutMlt8+bNu/322zu2bt26ZcuWjRs3znJ9idGijcCAEL4KVAGq/wf8bS1pQqhNAwAAAABJRU5ErkJggg==", + "description": "Preconfigured gauge to display any value reading as an arc. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 5, @@ -72,6 +81,8 @@ { "alias": "lcd_gauge", "name": "LCD gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as an arc. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 5, @@ -88,6 +99,8 @@ { "alias": "lcd_bar_gauge", "name": "LCD bar gauge", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAB5lBMVEVERERFRUVGRkZHR0ZHR0dISEhJSUhJSUlKSklKSkpLS0pLS0tMTEtMTExNTUxNTU1OTk1PT05RUVBSUlFTU1JUVFNVVVNWVlVXV1ZYWFZYWFdZWVhaWllbW1lbW1pcXFpdXVtdXVxeXlxfX11gYF5gYF9hYV9iYmBjY2FkZGFkZGJlZWNmZmNmZmRnZ2RnZ2VoaGVpaWdra2hsbGlsbGptbWpubmtubmxwcG1xcW5zc29zc3B0dHF1dXJ2dnJ3d3N3d3R4eHR4eHV5eXV6enZ7e3d8fHh9fXl9fXp+fnp/f3uAgHyBgXyBgX2Cgn6Dg3+EhH+EhICFhYCHh4KHh4OJiYSJiYWKioWLi4aMjIeNjYiOjomPj4qQkIqQkIuRkYySkoySko2Tk42Tk46UlI+VlY+VlZCXl5GYmJKYmJOZmZOampSampWbm5WcnJadnZeenpifn5mgoJqhoZqhoZuiopyjo5ykpJ2kpJ6lpZ6mpp+mpqCnp6CoqKGpqaKpqaOqqqOrq6SsrKWtraWtraaurqevr6iwsKmxsamxsaqysqqzs6uzs6y0tKy0tK21ta21ta62tq63t6+3t7C4uLC5ubG6urK+vra+vre/v7e/v7jQ0MrQ0MvR0cz09PP39/b39/f///+daHfNAAAAAWJLR0ShKdSONgAABFpJREFUeNrt3f9TVFUYx/HnriCSmF9ADUqyFqUFTTuRX5KCyoOKxopiwBpQKrG2C0HSjZBFJRfUxNqFfZMpIv9pP8jaMlM/5IyxZ3uen/acnbkzrzn389xzf7lHlpceL+B4LTx6sixLDymAergkjymIWpSFwoAsCAVSClGIQhSiEIUo5CVArvu+7/t+8rafLZjq+SblHGSbiIhIuFlWyktFi1/bunPGMUgmUBUOh8Ph0UQ8Ho/H4/2BenY08mCHhenGX92BTEvjqvE5iT2QAThaB4eDDq2IL2dyh3PbqjJprx8Ov0ts3ZhDkAG5ODXkZ7LDixKB4Dv3xjae/W27dSkjnVIuIhXDK4mp2pqGRJVIw/yJipRLkDap7ug2suEWAFE5DcDUPa4Hom6135vzwHFpBmDv+jvZbrangZu90TnHnux3pBrgmhzLznSXTfcVVW7a9cCxLUrJdgDjTayMZzZ9wcYWZja3OQLJHPwE4Bd5C5j09mfnDwe5JSNwsMGVFakrvgGExQKNMrgyG183RlKGYf8hVyBDgfKOy03eliRMF7+enf3yNLDTpH4o7nYmI1crRby9CeCkXFz1z2i5yNGMQ2G/P373HwI0eVdfrBSiEIUoRCEKUYhCFKIQUk3GmI9TwKgxxlxyEZI+ApyJGGMi7UBDmzHN9oaDkL5XgOY6oNYCRVcgIS5Cep5DaixQdBkmFLKWNVMLtAwC/a1A3SzMv+8ihCQwvfpX+p6DkOk3gBN9QO+nQDAJ6f3j/z3Ef/F6doErAcDuAeqbgLJemBKFrCFkIADYWuBAE1DWB7e9cQczkvkMuBAJhUKR80BTSyj0QceEq3utTNha2zYPTFhr7VXd/a5hRhSiEH1DVIhmRCHatRSiEA27QjQjCtGupRCFaNgVsgYQefFSiEI07ArRjChEu5aGXSEadoVoRhSiXUvDrhANu0I0IwrRrqVhV4iG/V9BuowxDZPuQ+LhY8aca3Q/IzGZh/bKAoGEFZI/GYmdBG4edB/y7AtcSfchw0eARI37GRmW+9BRoZD8gXiz0FkAkNEOEwq1WvchXLHW2lu6jVeIQhSiEIUoRCEK+btK2xGgM0K0FfjqHMN2ACBiE3R3MW5t68A8TNqfv7bWWmt/siPA55F8g8zKeaBmH7YMOFRJu+zMwGyJDBCqpV/q60uDs8Rk9IIxXpUxyeo34Vvpz39IiTcCkdIs5DpTG5uIySggbUBMrrJndyb/IZv3fQg1R/6C8NGWXAi1we9kiPyDiIhIDmTD5dLUDS+aAzkrqVzINa+8jjyEHPd9f1duRtKv9pzanciBnFy3akV4T/x8hKzcWm2BDBwI0i5zzbUVnWM5kLeDqyFnJJPHkO+9U7ej61tpl7kfZf3955BLQ0e9QZcgdG2RosY07TJH9SGeQ6S4ZhAHILl19+V9vFu3KApRiEIUohCF/K8gBXJA8O/yqDAgi/KkIA7R/uOpLC8tun+s+eLT5T8Bm8H0V8ljg20AAAAASUVORK5CYII=", + "description": "Preconfigured gauge to display any value reading as an bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2, @@ -103,7 +116,9 @@ }, { "alias": "simple_neon_gauge_justgage", - "name": "Simple neon gauge - justGage", + "name": "Simple neon gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a doughnut. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 3, @@ -119,7 +134,9 @@ }, { "alias": "vertical_bar_justgage", - "name": "Vertical bar - justGage", + "name": "Vertical bar", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABQVBMVEVqampra2tsbGxtbW1ubm5vb29wcHBycnJzc3N0dHR2dnZ3d3d5eXl6enp7e3t8fHx+fn6BgYGDg4OFhYWHh4eIiIiKioqNjY2Ojo6RkZGTk5OWlpaZmZmbm5uenp6fn5+goKCkpKSlpaWmpqanp6eoqKiqqqqrq6usrKytra2xsbGysrK0tLS1tbW2tra4uLi6urq7u7u9vb2+vr7AwMDBwcHExMTFxcXHx8fIyMjJycnKysrMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT09PU1NTV1dXX19fY2NjZ2dnb29vc3Nzd3d3f39/g4ODh4eHj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fHy8vLz8/P09PT1fAD19fX29vb39/f6+vr7+/v8/Pz9/f3+/v7///8uFkw3AAAAAWJLR0RqJWKVDgAAAkBJREFUeNrt3ElTE3EQhvGebKMIiUExGkSNuCEiKAqKRKJsIuISN0giwQFC5v3+H8BD5uDBE2WV9tTTp1x/lTyd/6lNKRkDAgQIECBA/jtIHEXx4FOv3fMMWTSLJOn9eN4+OoZ0zg4gjZxduvXDMWQim7FIaoe5hutGXtn90CJp2mZcx340cv44tEgas9kLufKyW8ht21BokVSwTPWKBdtOIdvBDSm0SH2zFWnaxn1CeqPB03o9b88+9C2IpV0r+YTsWDJ3NWQdqWNDPiGH9Xp98I3ous1Ji1b1/NYKLZI+FzKTd3LBG/cQbZXNii9T8frttGKe8UCAAJGaS5J6j2/O96TuzOTz2CfkxZhVJF0tTY1MqF++eK/w0Cdkar5akdrBa60EnTXb04NzXn9a1yrSln1V094+yUvLduQYsmp7+m5rs2ekhrUdQ9ZtV99sYy6UGrbvGPLJdvTOmkvBsRay8T+CtE45v0NOirVurdRvZR/tX67KMUSbwza8KS2ENvrFKySZA0lSfMgTBUjqIH+pESBAiB0IjQBhawEBQuxAaAQIWwsIEGIHQiNA2FpAgBA7EBoBwtYCAoTYgdAIELYWECDEDoRGgKQK8vOUAwQIsQOhESBsLWIHQuxAaAQIW4vYgRA7EBoBwtYidiDEDoRGgLC1iB0IsQOhESBsrXTHnlws9g8ZXCz230hysdg/JLlY7B+SXCz230hysdg/JLlY7B9yUqx1a8V+CtZvcrE4Df/sBzxRgAABAgQIECB/mF+DKtRemhqPQgAAAABJRU5ErkJggg==", + "description": "Preconfigured gauge to display any value reading as a bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2, @@ -135,7 +152,9 @@ }, { "alias": "simple_gauge_justgage", - "name": "Simple gauge - justGage", + "name": "Simple gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a circle. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2, @@ -152,6 +171,8 @@ { "alias": "digital_bar", "name": "Digital horizontal bar", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a horizontal bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 6, @@ -167,7 +188,9 @@ }, { "alias": "mini_gauge_justgage", - "name": "Mini gauge - justGage", + "name": "Mini gauge", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a circle. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2, @@ -183,7 +206,9 @@ }, { "alias": "horizontal_bar_justgage", - "name": "Horizontal bar - justGage", + "name": "Horizontal bar", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a horizontal bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 7, @@ -200,6 +225,8 @@ { "alias": "digital_vertical_bar", "name": "Digital vertical bar", + "image": "data:image/png;base64,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", + "description": "Preconfigured gauge to display any value reading as a vertical bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2.5, diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json index 12ffd8b82d..410e70714b 100644 --- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "entity_admin_widgets", "title": "Entity admin widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Templates of complex widgets that allow to list and create/update/delete devices and assets." }, "widgetTypes": [ { "alias": "device_admin_table", "name": "Device admin table", + "image": "data:image/png;base64,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", + "description": "Customized entity table widget with preconfigured actions to create update and delete devices.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -16,14 +19,16 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" } }, { "alias": "asset_admin_table", "name": "Asset admin table", + "image": "data:image/png;base64,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", + "description": "Customized entity table widget with preconfigured actions to create update and delete assets.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -32,10 +37,10 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" } } ] -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json index 22cbc82beb..313dd52323 100644 --- a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "gateway_widgets", "title": "Gateway widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Widgets to manage ThingsBoard IoT Gateway." }, "widgetTypes": [ { "alias": "gateway_configuration", "name": "Gateway Configuration", + "image": "data:image/png;base64,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", + "description": "Allows to define widget gateway configuration for a single selected device.", "descriptor": { "type": "static", "sizeX": 8, @@ -24,6 +27,8 @@ { "alias": "attributes_card", "name": "Gateway events", + "image": "data:image/png;base64,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", + "description": "Allows to browse events from the gateway.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -40,6 +45,8 @@ { "alias": "config_form_latest", "name": "Gateway configuration (Single device)", + "image": "data:image/png;base64,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", + "description": "Allows to create or choose the gateway device and edit the configuration.", "descriptor": { "type": "latest", "sizeX": 7.5, diff --git a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json index 00a3c7b3f3..6ca5222f16 100644 --- a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "gpio_widgets", "title": "GPIO widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Visualization and control of GPIO state for target devices." }, "widgetTypes": [ { "alias": "basic_gpio_control", "name": "Basic GPIO Control", + "image": "data:image/png;base64,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", + "description": "Allows to change state of the GPIO for target device using RPC commands. Requires handling of the RPC commands in the device firmware. Uses 'getGpioStatus' and 'setGpioStatus' RPC calls", "descriptor": { "type": "rpc", "sizeX": 4, @@ -24,6 +27,8 @@ { "alias": "gpio_panel", "name": "Basic GPIO Panel", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAMPklEQVR42u2dB1AUWRrHcUFUUAFFHMFRoqBYgCIlhpM1g/nUM2upa/bMAcsrU5X5zrPUQj1dC0Fl1eNMJBlFkQUBLTGsuuYARkygmADl/sur7e2bQaYZRwz3/9WrrZ7e5onzfvN9r9+8rzUpKioqKCjIzs7OzMy8RchHAIUgUn5+PqQygVVZWVm5ubmFhYVFhHwEUAgiQSdIZQLF8IJvCjEW0AlSmSB8MVYR48YtSGWC1Mj3ghgXSEWxCMUiFItQLIpFKBahWIRiEUKxCMUiFIsQikUoFqFYhFAsQrEIxSKEYhGKRSgWIRSLUCxCsQihWIRiEYpFCMUiFItQLEIoFqFYhGIRQrEIxSIUixCKRSgWoViEUCxCsQjFIoRiEYpFKBYhFItQLEKxCKFYhGIRikUIxSIUi1Cs8iApICCubl02Je1BbCzFolgUi2JRLIpFsSgWxaJY35ZYIW61R/pad/ev2qt51b/6WIc7qT7dYMd71kkcZ528xDJltUXS/GoJvW3j6jtQrG9NrN2O9u1bWdQMNJW32oFmQ/2sotXGHml13cRR1ulxZic0pvKWur3SoVYqivUbR44cGTx4cIsWLQYMGHDq1ClxMjQ09C+/M2XKlHPnzonzw4YNu3HjhjhOSEjo2bOnv7//uHHj7ty5o9tzbm7u5s2bN27cWA5i/bu+feO2lbSsklpgS8tYo7p1bLqVllJSS482+xRulSjW/fv3p0+fHhAQ0Llz502bNr1//x4nr169Ko3diBEjduzYIc5jLMLDw8UP3rt3b8KECRi7Hj16aDQarW7fvXu3bt269u3bt2vXbsOGDYaIBTmsra23bNly8eJF/NfGxgYHOA+ZevXqdaiYVatWVa9ePSMjA+elg6ioqJo1a8K/48ePT506Va1WP336VN7zggULateu7eLiAu0MECvBx+fXhQuzIiIUvu9dW1T9kFWizfCyMdYYH+5kdyLe9ENiibgVV19RVxp397uRkY+Tkx8nJZX+MdMV6+XLl+7u7mPGjMGIHDt2zMvLa8mSJTifnp6OYRJjFxkZ2bhx47lz5+L8+PHjZ86cKT7wjo6OkyZNwtiFhYXZ2tru2bNH3vPq1av9/PzOnDmDrurVq7dr164yi9WxY0fx2whmzZq1cuVKIdbkyZOl85Bj2rRpcrHw14BV0gVBQUHyfkBqair+5vPmzTNMrHv79l1eujTn9GklwxPmrLIt1So0t/bmsUYSK/kfFqVYJVpCD1slXV1ZufLS4sU4ODVy5P2oqDKJhfff09NTRCOQnJzcqlUrIRY+89JlcA6fcLlYK1aswLhLF2zbtq1Ro0byngcNGhQdHS2OEREROMoslr29fUpKiu55LbEQUeGcJNazZ89MTEwePnwoXbBmzRq4pduPwWKhHfXzUygWolFNfWKhbXQ1QoY6qK6rO7XSbUl/q6akt0R//0MeHjg40b9/5rZtZRILliCd6b6rWmIhbjk4OMjF6tKlC2KSdMGjR48wmk+ePClxjFq3bh0REVFmsSpUqHD58mUcIAPuLiYtLU2Ihfz6r2JwjHQpUqQQ69q1a9999528n507dyJ4fi6xhvtaKRFraaNaRrkT1GsVWso/LcrQp4vL0/T0pO+/L5NYQ4YMwXxD5MTdv4MABrEsLS3F2CH/IOstX75cLlbz5s21XDEzM7ty5YruAK1du7ZDhw6YcpVZrKpVq+L3EFP44ODgNm3aDBw4UIjl6+sbXAyi0d27d8X1QqwHDx7A8ZycHKkfzNAh4ucSa0wTRWKtcrczQsRydVAiFtYglHZYrx6S4JmJE8s6eR87dqzIKkggGCa8zxgUSIABrVKlihi7xYsXY/ImrpfEwqx8/fr1Uj8vXrzAD2I6r9U/sqGTkxPuDwyZvCPQyaMifg9JLHkqlBBi4WOBHBofHy+dHzVqlJiEfRaxljaspdeqWp1Nd9e3N8ocK22nuV6xEidaK+zt1o8//rpokQF3hbhfa9asmfTy5s2bkljyVCghiTVjxgzc3cuXBVQqlVZYQuLCtF2kKUPE2rdvn52dnUh/b9686d+//9ChQ/WKhQPEWMwckRPxCx04cMDKyurSpUufS6wDagfX9uali9WplaXR1homW+sRK95U01TRfO6XGTNe37t3PSRENNwkKhcLGQOTp2XLlhUUFOAl8iAymhKxMGoYL1yPi2/fvt20aVMEFPmVGEokUJHKDF/Hwk0BFgVq1KiBP6x3795iRWrOnDkIpLoXw+KzZ8/iAEELvw3uVCtXrowbWswQS+wct4q4rTBMrHgnp9KnHfK2wNO2lBtDh45moS5GW1uKb2CfFlGptJn7TCuFXR3x9U0JCpLaQUfHMq1jYWKEvFa9GB8fn5iYGJzEYiQGVPdiBCp8zsVxYmIirkfGxLjjpFa4Gj16NG4knWUYvvKOcGXY8mt+fv4XsvI+ydvatnMJVqk7Vvy7h51xlysPtVSl7So5ISYvtfwUX+yUsvJeUEw5j93/13eFaxvUbtvawq7zH4Hqz/7VtjnX+RTfsRxsZJ80t1r6gT+WHlLDKx0dUiNOze8Kv9HdDXvrOWx2USH3RdV1+NSbDg46O2haqA53sNM0UX0ipSgWt81QLIpFsSgWG8WiWBSLYlEsisVGsSgWxaJYFItiUSyK9ZnFinVQR9dxjq7jFFdXTbEolhHaAVWDCMuAULNuoaY90MIqdtlVvWWxYRSLYhna/mPjs9W0u1BK3qDXfjt3ilXeYqFsa/v27YsWLUIl0KtXr8RJVFiITdOoVouNjX379q04j8oQ7L2XfhA7krHtGhv9dLdFY8MWdhGi261bt0o/rlwsjZvb6fHjM8aMiXd2VvK+77P11FVKalsrdo1WOVOs8hML57ELEZWN2KDcr1+/hg0bYgN1kWzP++zZs1EqhPq1x48fF8l2kKIEA7uTseN0/vz5TZo06datW2Fhobxn7MhGISW2z/bp06dTp05lEgtb/J5lZFxcsAAVYI9//ln/++6gDjcPLEUstJ8s/0Sxyk8sVHoMHz5cegkDUHRWpLM1Gd4sXLhQLlbXrl3hnPi/r1+/9vDwQMCTrkeIcnNzE7ENx+bm5lI5hhKxEry9f5k5UxznXb8Oz/SEq1qepVv1W9Ay7R5j70ixykks7GE9fPiwPC2KakEtsbC9eOLEiZJYyJgVK1aUau2Lircg9+3bt8Q/AruqUQskL+lROMdK7dkTxdCoXNVfX2/VTK9YaPvtGlKschILfohKDESdp8Xk5eUJsVC7jZeo9IqLi0PBhSiNFWKhIAQFifLch1kanv6g1fnRo0dR84OMiUJvAybv54OD7+7Zczs0VP/jQKyaKxEL8zCKVU5iIUmJ4oiQkBDsmcfWeqn8C7UVOIMcFxgYKFXvC7EQhCCWfJs88iAqybQ6z8rKwvZ+hDrUspa4vfpDYmlcXfHsBnGMyRbKhUt/0yNtmigRK0rVgGKVk1iYraOIWXqpsK4QlqDWVhgpwGMn5KVqCGbiJkAAQVEWolwsmPT8wgXUnid4eeVdu6ZpoEeIKJWLXqvCzIO+6vXSr0wsVKWhrOz58+eSH0rEwsHIkSNxLymyIYplke+QMeVzNdS4iSJaJFlUiV24cKFMqTDjhx+yExIeajTpffsqed9/smxTuliRNk15V1h+YiH24MYQ1YJ4RBaWFTCXEk9L0isWniGB3IeAh0dk4afEg3LkYH0LdwaYqHl7e5f47ArjLpDG2DuFV/rgikOERcDX/vXOV7nyjkWpvXv34nk30gJpZjG6V54+fRqPoJCWQE+ePInV0Q91jieA4TFa58+fL5+V95g6TjuqtNWxqju+1cG3h1x551c6H9X223nstvKPsGiDFdFIK78olSu/hKZYbBSLYlEsikWxKBYbxaJYFOvLFKvw1avCvDw2Je39/+5Koljkm4ViEYpFKBahWBSLUCxCsQjFoliEYhGKRSgWIRSLUCxCsQihWIRiEYpFCMUiFItQLEIoFqFYhGIRQrEIxSIUixCKRSgWoViEUCxCsQjFIoRiEYpFKBYhFItQLEKxCKFYhGIRikUIxSIUi1AsQigWoViEYhFCsQjFIhSLEIpFKBb5BsXKzMws/PL+tWry9QKdIJVJdnZ2bm4u3w5iLHJyciCVSX5+flZWFtxi3CIfH6sgEnTCgQleFxQUQDGEr1uEfARQCCKJCPVfs7q/gTbG/uYAAAAASUVORK5CYII=", + "description": "Allows to display state of the GPIO for target device using latest attribute values. You should set the label of the selected data key to GPIO pin number (e.g. '1') and use boolean values for widget to display the data.", "descriptor": { "type": "latest", "sizeX": 5, @@ -40,6 +45,8 @@ { "alias": "raspberry_pi_gpio_panel", "name": "Raspberry Pi GPIO Panel", + "image": "data:image/png;base64,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", + "description": "Allows to change state of the GPIO for Raspberry Pi device using RPC commands. Requires handling of the RPC commands in the device firmware. Uses 'getGpioStatus' and 'setGpioStatus' RPC calls", "descriptor": { "type": "latest", "sizeX": 7, @@ -56,6 +63,8 @@ { "alias": "raspberry_pi_gpio_control", "name": "Raspberry Pi GPIO Control", + "image": "data:image/png;base64,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", + "description": "Allows to display state of the GPIO for target Raspberry Pi device using latest attribute values. You should set the label of the selected data key to GPIO pin number (e.g. '1') and use boolean values for widget to display the data.", "descriptor": { "type": "rpc", "sizeX": 6, 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 42a7e0a00f..b19c5d40b9 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 @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "input_widgets", "title": "Input widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Various input forms that allow users to set location, image and other configuration parameters of the target entity" }, "widgetTypes": [ { "alias": "markers_placement_openstreetmap", "name": "Markers Placement - OpenStreetMap", + "image": "data:image/png;base64,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", + "description": "Allows configuring location of the selected entities on the OpenStreetMap.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -24,6 +27,8 @@ { "alias": "update_multiple_attributes", "name": "Update Multiple Attributes", + "image": "data:image/png;base64,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", + "description": "Allows to create an input form and set values for multiple attributes simultaniously.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -40,6 +45,8 @@ { "alias": "device_claiming_widget", "name": "Device claiming widget", + "image": "data:image/png;base64,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", + "description": "Allows to claim the device using name and optional secret key.", "descriptor": { "type": "static", "sizeX": 7.5, @@ -56,6 +63,8 @@ { "alias": "markers_placement_image_map", "name": "Markers Placement - Image Map", + "image": "data:image/png;base64,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", + "description": "Allows configuring location of the selected entities on the image map.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -72,6 +81,8 @@ { "alias": "update_integer_timeseries", "name": "Update integer timeseries", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANBSURBVHja7d3dS1NhHAfw/Sum1IUQBZYJIdSF4VVQIOSNXZiJg5nmWi18CXG9WJgSqd1IBUWu10PY1EBcIFYqyoTCLOZWA8/0uGmbbjs73y7mS+Z2YdjYs76/i3Oe88C5+PB73jhwnkeHiOxyCh4uOQxdxO1XIXiofndEJ/uRBuGXdS41HSCqS+dEWoSTEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYSQfwl59xkA+teqDfLWl9wOESC9twAo5QsJIdHI6EstkvoQX4UP6LsNzNo/hgGDrI2oCI0B0XG7DADuSzcttaMC9JG2t0DTMEZqezuvAwY5UhbEvB5a213J9A0Awg0mnwid/cM1zBpCmPQgWrGwARlv1GDvBABbd88TESDhc3Ov7wM/H99oKp/dgEgXWlqaGgFAWYl6hBh+H9rqp4COV1EYYpAA5vWwdXi9XkWoeeRLjVkDGofxqdwDg4zqrxjTY+b8HDwOoSDaZQnAeNXFVuMEDDIGKs1temCwpsE8JeLMHg2u95qVGHBR4xKFEEIIIeT/gyxvKYgB0d5sXhAtN0uxgtS8LBJEsxrtm2sko/TbTRSI9tzY92ddj1FavYgD0axbHYBklOLnI2Uh8fIRy0kCR8pBgvdcALRn8R3bhMjdrdYlAFh62mpd/Uk81J8ciGKp/56gXW27aQ3uPVqac9AJTOUePpOfMw1gom5/VpJSsnC1zpU4H9vp7KEDlSqUQ3qg6GQI4cKzwIvMU6eTBYFiMSXOx3aG38mM9wCMBUDDAIAr+YDzB9qTBoFiie/4uwmxuGJ1+CgqAYBkQhDcwSVKX+YIANi6SvKmkw7ZwUXjzL5GAEDlsT2l8wJD5o+UrO1o4S0sFhcSOH4isP7wYFdAVEikpEABgGBWF4D27KigEK06u9/hcDgUlOUNLQ7lmkRtWksZsXiEhaqsjN3mgMij1lqEPcJsY8OPD4QQQgghhOws5E5SgxkhhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCElNSNocEJweRzb7ZF04PQ7RVnXpcay5il+KVmW7YpZ2rQAAAABJRU5ErkJggg==", + "description": "Simple form to input new integer value for pre-defined timeseries key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -88,6 +99,8 @@ { "alias": "update_double_timeseries", "name": "Update double timeseries", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANMSURBVHja7d3fS5NRGAfw/SsrhSCKKIugburCi6KCLrropi4izME0c1kLUxquHybmKNPAi4qMJkm9hOkswhVkpaLMfhmlbjHYu+3Vaf7a3r3fLjad5nZh6NiZ3+diO+fAe/HhOc85Z+/Fjg4R2T0ieLjlMHQRT0iF4KGGPBGdHEIWREjWudVsgKhu3QiyIkYIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQghZS8jbbwDQOT9slJc/5HGJAOmoAaAUjKWERCO9z7RI5kPGC8cBxy3A5/wUBoyy1qNirg+I9jtlAPBcvGkt7xWgRmyvgKpu9JR3NF4HjHLk9DSCBmi2eqnsFwCEK8vGRSj2j9fgM85h0Ito4VgC0m/R4GwEgHZ72xMRIOHiwIv7wJ/HN6oKfAmIdL62tsoCAMps1CvE8vuwvWIIaHgehTEGmULQgPYGv9+vCLWP/Cg1a4ClG18LvDDKKPmJPgNGzwXgdQkF0S5JAPrPXqgzDcAo402R2WYAukorzUMi7uzR6YWqmY0BJzQeUQghhBBC1h9kZllDDIj2cumBaKZaijWk6hmRIFqLybl0RDJJi75EgWitJse/Y20mKf4hDkRrWe4AJJOUPB8ZC0mWj1hOUjgyDjJ9zw1Ae5rcsUKIbK9rmVzovatv+hJvfh5ec4hirfidYl6teGp1bd1/Km9XfDhasOnksZxGAIGmA/rWtU/J2NXL7tT5WEmxz+0sUqHsNsR69lwXcDc3AC13b0U6IFCsZanzsZLld1D/AYApP/5wDYBBfTfwPupPCwSKNbnj/zbE44WLOvYcLwCkCYLpVTyiODb2zDe/P7qypQnphKzioXF0myUxMw9t3+MUFBLcd2LxP1qo1bnDQkKmjhydWjqw4YGIkMiJ/MT5ufggAL9eEhCilWzudLlcLgWvbYBDfyfoPpOnCAiZ1MeiGeYdKtCcp9cfHhB11YoXeewFo2+Cv9kJIYQQQghZf5DbaQ1mhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCEkMyFZc0FwdlzZPC7rwtlxibaqy45rzVX8BUWnY6Q1/jMIAAAAAElFTkSuQmCC", + "description": "Simple form to input new double value for pre-defined timeseries key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -104,6 +117,8 @@ { "alias": "update_boolean_timeseries", "name": "Update boolean timeseries", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAIQSURBVHja7d3PSxRhHMfx+VcWgmg3ltKD7BJshyBIQusQ3vIYCILCRMyhH6xShlGWMGq1p1gIibxFULRR1F4ixcNqdNhmUXFipWnTYvfZ/TrjJqFQ13ge3p/L8MztBQ/P8/0eZr6WNHyvrHk8vy5WoxIo0TwqqDQsPxADEviWp0yAKM8qixEpAwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC5H9DSiP2noyUNIXsc9j2qKYQ2/732lzI7LyukN4jUV7uLrtndIWcuP4lzKYBELf9rD66/bzZhqw9vFcM37ReTzz5pR3kc8fAWPriDmT58NDl5F0R51j2zOm6PpB+13VfyNNbIm8O/Iwg430iz87Lu/iaNDKz+kB6Hcd5HH60PDedjfkR5FXi2oeWyI3j+Xy+56puW2vh6KXcnTZEFq90pQripAbC5HSDDDoin9qQjxVRkwk1dUrLU2v4bLAxGFuNIEPnanK/s7WSeNDcct7rBvFOxg7djBUjSLX/YDJdEHmbiccv1PQrUb7++T/B9/Xfd8sPikbK+L81VqP7HEu0ukCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAADEUYsyAYDNGNn/zrboZQ7SVZcZYcyXbTFGWg8aBYQQAAAAASUVORK5CYII=", + "description": "Simple form to input new boolean value for pre-defined timeseries key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -120,6 +135,8 @@ { "alias": "update_string_timeseries", "name": "Update string timeseries", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAOkSURBVHja7d3bSxRRHAfw/VdG6yklCbPsRk+GhVLYDSXMohC1tczVUkxFXDULzW4a+SA9VIZRm5haIJqBmMqW2cUoddUVR1sved2dnV/nuJaX3X0Q1pjZvj/YM4fDmYfP/uZcdvbhaMgmmnpVHibRShpb/4REKg9pot+mESfIC2JC1Jgkb4BIJk0veUX0AgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAsp6QN194+epPs1Z0vqm/Uw2QuuussMSOuYXYbe3PZJvyIeNx40T1xUTDTe+sHCK3STTfwQDGpgVU/+Vr+ox2FYyRktdEuS3UllFXVsAhtrMz9DOe5JI7htQfvIM1K3VcDYO9NZ+GtfP00Uz2uLEliDFHpqYy3qG2suaxGiDW86PVFURTD6/mxg4vQQwpRUW5ObyDZc5uVsX0+6A2s5uo9LmdtA7INIfUlo6MjFhUtY58S06TiXJa6HOsmc9aSd+pI576Lo6SuVNVEDndwErjhUs3dO85pCExrSSeqDE5K61bjSu7febvqJlzACdlbFEAAQQQQP4/yKxTRR0Q+eXKDdFsocFRMRTOqgkiP9E1rWwx6AzLLmqByE919avbajihxqVDsRCWj3rnVpYM1/lQLMRVPhw5ceNQHGTmnok7qlw7PAuJOrWOEIs+c8DNc+XxR2tdITSWd8XkPh8eHezrC2E5SXWfjzVNvxUdRn0Bf29V3sZvq2GVD82FuUYy5ukbFiBdN7MbeU+pOvvuV96xoyrT4kGJa8eaF8Sg/cHaEN9WIr/ixe9/8+7QxD0bsgITDgqPWMu2refChXIi28mApIiNzOYX7B826MEB76EtStC+SbLuSFkOiZZoaos/ewcUdYh9AodJ1gX8ovubekhO2Ms6hk8pcdMYlM+KmBPLIUWscoy1UF7Q4hjpEpopTMsq9cKYo6NCIacjV0MiY1hRELgImRQqKUBYiC5VQ4aEatqZ2MljWvGQYP6C9LAz5CirvBA+UUyYndXmSPGQ2GDjwG0fZ4hQPPh21xH2l5BP+oCoD7UrHtITIvgej3CGnIkWhAN9fELfLggh7YrNyLIQp92sWUOOq31QxG92QAABBBBAvBNy658GMgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAokyI1xwQ7B1HNo+LGqt3HKItabzjWHOJfgM5tA7UOw6xeQAAAABJRU5ErkJggg==", + "description": "Simple form to input new string value for pre-defined timeseries key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -136,6 +153,8 @@ { "alias": "update_server_image_attribute", "name": "Update server image attribute", + "image": "data:image/png;base64,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", + "description": "Simple form to input new image for pre-defined server-side attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -152,6 +171,8 @@ { "alias": "update_server_date_attribute", "name": "Update server date attribute", + "image": "data:image/png;base64,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", + "description": "Simple form to input new date value for pre-defined server-side attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -168,6 +189,8 @@ { "alias": "update_server_boolean_attribute", "name": "Update server boolean attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAIQSURBVHja7d3PSxRhHMfx+VcWgmg3ltKD7BJshyBIQusQ3vIYCILCRMyhH6xShlGWMGq1p1gIibxFULRR1F4ixcNqdNhmUXFipWnTYvfZ/TrjJqFQ13ge3p/L8MztBQ/P8/0eZr6WNHyvrHk8vy5WoxIo0TwqqDQsPxADEviWp0yAKM8qixEpAwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC5H9DSiP2noyUNIXsc9j2qKYQ2/732lzI7LyukN4jUV7uLrtndIWcuP4lzKYBELf9rD66/bzZhqw9vFcM37ReTzz5pR3kc8fAWPriDmT58NDl5F0R51j2zOm6PpB+13VfyNNbIm8O/Iwg430iz87Lu/iaNDKz+kB6Hcd5HH60PDedjfkR5FXi2oeWyI3j+Xy+56puW2vh6KXcnTZEFq90pQripAbC5HSDDDoin9qQjxVRkwk1dUrLU2v4bLAxGFuNIEPnanK/s7WSeNDcct7rBvFOxg7djBUjSLX/YDJdEHmbiccv1PQrUb7++T/B9/Xfd8sPikbK+L81VqP7HEu0ukCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAADEUYsyAYDNGNn/zrboZQ7SVZcZYcyXbTFGWg8aBYQQAAAAASUVORK5CYII=", + "description": "Simple form to input new boolean value for pre-defined server-side attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -184,6 +207,8 @@ { "alias": "update_server_double_attribute", "name": "Update server double attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANMSURBVHja7d3fS5NRGAfw/SsrhSCKKIugburCi6KCLrropi4izME0c1kLUxquHybmKNPAi4qMJkm9hOkswhVkpaLMfhmlbjHYu+3Vaf7a3r3fLjad5nZh6NiZ3+diO+fAe/HhOc85Z+/Fjg4R2T0ieLjlMHQRT0iF4KGGPBGdHEIWREjWudVsgKhu3QiyIkYIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQghZS8jbbwDQOT9slJc/5HGJAOmoAaAUjKWERCO9z7RI5kPGC8cBxy3A5/wUBoyy1qNirg+I9jtlAPBcvGkt7xWgRmyvgKpu9JR3NF4HjHLk9DSCBmi2eqnsFwCEK8vGRSj2j9fgM85h0Ito4VgC0m/R4GwEgHZ72xMRIOHiwIv7wJ/HN6oKfAmIdL62tsoCAMps1CvE8vuwvWIIaHgehTEGmULQgPYGv9+vCLWP/Cg1a4ClG18LvDDKKPmJPgNGzwXgdQkF0S5JAPrPXqgzDcAo402R2WYAukorzUMi7uzR6YWqmY0BJzQeUQghhBBC1h9kZllDDIj2cumBaKZaijWk6hmRIFqLybl0RDJJi75EgWitJse/Y20mKf4hDkRrWe4AJJOUPB8ZC0mWj1hOUjgyDjJ9zw1Ae5rcsUKIbK9rmVzovatv+hJvfh5ec4hirfidYl6teGp1bd1/Km9XfDhasOnksZxGAIGmA/rWtU/J2NXL7tT5WEmxz+0sUqHsNsR69lwXcDc3AC13b0U6IFCsZanzsZLld1D/AYApP/5wDYBBfTfwPupPCwSKNbnj/zbE44WLOvYcLwCkCYLpVTyiODb2zDe/P7qypQnphKzioXF0myUxMw9t3+MUFBLcd2LxP1qo1bnDQkKmjhydWjqw4YGIkMiJ/MT5ufggAL9eEhCilWzudLlcLgWvbYBDfyfoPpOnCAiZ1MeiGeYdKtCcp9cfHhB11YoXeewFo2+Cv9kJIYQQQghZf5DbaQ1mhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCEkMyFZc0FwdlzZPC7rwtlxibaqy45rzVX8BUWnY6Q1/jMIAAAAAElFTkSuQmCC", + "description": "Simple form to input new double value for pre-defined server-side attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -200,6 +225,8 @@ { "alias": "update_shared_image_attribute", "name": "Update shared image attribute", + "image": "data:image/png;base64,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", + "description": "Simple form to input new image for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -216,6 +243,8 @@ { "alias": "update_shared_date_attribute", "name": "Update shared date attribute", + "image": "data:image/png;base64,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", + "description": "Simple form to input new date for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -232,6 +261,8 @@ { "alias": "update_shared_boolean_attribute", "name": "Update shared boolean attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAIQSURBVHja7d3PSxRhHMfx+VcWgmg3ltKD7BJshyBIQusQ3vIYCILCRMyhH6xShlGWMGq1p1gIibxFULRR1F4ixcNqdNhmUXFipWnTYvfZ/TrjJqFQ13ge3p/L8MztBQ/P8/0eZr6WNHyvrHk8vy5WoxIo0TwqqDQsPxADEviWp0yAKM8qixEpAwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC5H9DSiP2noyUNIXsc9j2qKYQ2/732lzI7LyukN4jUV7uLrtndIWcuP4lzKYBELf9rD66/bzZhqw9vFcM37ReTzz5pR3kc8fAWPriDmT58NDl5F0R51j2zOm6PpB+13VfyNNbIm8O/Iwg430iz87Lu/iaNDKz+kB6Hcd5HH60PDedjfkR5FXi2oeWyI3j+Xy+56puW2vh6KXcnTZEFq90pQripAbC5HSDDDoin9qQjxVRkwk1dUrLU2v4bLAxGFuNIEPnanK/s7WSeNDcct7rBvFOxg7djBUjSLX/YDJdEHmbiccv1PQrUb7++T/B9/Xfd8sPikbK+L81VqP7HEu0ukCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAADEUYsyAYDNGNn/zrboZQ7SVZcZYcyXbTFGWg8aBYQQAAAAASUVORK5CYII=", + "description": "Simple form to input new boolean value for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -248,6 +279,8 @@ { "alias": "update_server_integer_attribute", "name": "Update server integer attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANBSURBVHja7d3dS1NhHAfw/Sum1IUQBZYJIdSF4VVQIOSNXZiJg5nmWi18CXG9WJgSqd1IBUWu10PY1EBcIFYqyoTCLOZWA8/0uGmbbjs73y7mS+Z2YdjYs76/i3Oe88C5+PB73jhwnkeHiOxyCh4uOQxdxO1XIXiofndEJ/uRBuGXdS41HSCqS+dEWoSTEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYSQfwl59xkA+teqDfLWl9wOESC9twAo5QsJIdHI6EstkvoQX4UP6LsNzNo/hgGDrI2oCI0B0XG7DADuSzcttaMC9JG2t0DTMEZqezuvAwY5UhbEvB5a213J9A0Awg0mnwid/cM1zBpCmPQgWrGwARlv1GDvBABbd88TESDhc3Ov7wM/H99oKp/dgEgXWlqaGgFAWYl6hBh+H9rqp4COV1EYYpAA5vWwdXi9XkWoeeRLjVkDGofxqdwDg4zqrxjTY+b8HDwOoSDaZQnAeNXFVuMEDDIGKs1temCwpsE8JeLMHg2u95qVGHBR4xKFEEIIIeT/gyxvKYgB0d5sXhAtN0uxgtS8LBJEsxrtm2sko/TbTRSI9tzY92ddj1FavYgD0axbHYBklOLnI2Uh8fIRy0kCR8pBgvdcALRn8R3bhMjdrdYlAFh62mpd/Uk81J8ciGKp/56gXW27aQ3uPVqac9AJTOUePpOfMw1gom5/VpJSsnC1zpU4H9vp7KEDlSqUQ3qg6GQI4cKzwIvMU6eTBYFiMSXOx3aG38mM9wCMBUDDAIAr+YDzB9qTBoFiie/4uwmxuGJ1+CgqAYBkQhDcwSVKX+YIANi6SvKmkw7ZwUXjzL5GAEDlsT2l8wJD5o+UrO1o4S0sFhcSOH4isP7wYFdAVEikpEABgGBWF4D27KigEK06u9/hcDgUlOUNLQ7lmkRtWksZsXiEhaqsjN3mgMij1lqEPcJsY8OPD4QQQgghhOws5E5SgxkhhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCElNSNocEJweRzb7ZF04PQ7RVnXpcay5il+KVmW7YpZ2rQAAAABJRU5ErkJggg==", + "description": "Simple form to input new integer value for pre-defined server attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -264,6 +297,8 @@ { "alias": "update_server_string_attribute", "name": "Update server string attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAOkSURBVHja7d3bSxRRHAfw/VdG6yklCbPsRk+GhVLYDSXMohC1tczVUkxFXDULzW4a+SA9VIZRm5haIJqBmMqW2cUoddUVR1sved2dnV/nuJaX3X0Q1pjZvj/YM4fDmYfP/uZcdvbhaMgmmnpVHibRShpb/4REKg9pot+mESfIC2JC1Jgkb4BIJk0veUX0AgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAsp6QN194+epPs1Z0vqm/Uw2QuuussMSOuYXYbe3PZJvyIeNx40T1xUTDTe+sHCK3STTfwQDGpgVU/+Vr+ox2FYyRktdEuS3UllFXVsAhtrMz9DOe5JI7htQfvIM1K3VcDYO9NZ+GtfP00Uz2uLEliDFHpqYy3qG2suaxGiDW86PVFURTD6/mxg4vQQwpRUW5ObyDZc5uVsX0+6A2s5uo9LmdtA7INIfUlo6MjFhUtY58S06TiXJa6HOsmc9aSd+pI576Lo6SuVNVEDndwErjhUs3dO85pCExrSSeqDE5K61bjSu7febvqJlzACdlbFEAAQQQQP4/yKxTRR0Q+eXKDdFsocFRMRTOqgkiP9E1rWwx6AzLLmqByE919avbajihxqVDsRCWj3rnVpYM1/lQLMRVPhw5ceNQHGTmnok7qlw7PAuJOrWOEIs+c8DNc+XxR2tdITSWd8XkPh8eHezrC2E5SXWfjzVNvxUdRn0Bf29V3sZvq2GVD82FuUYy5ukbFiBdN7MbeU+pOvvuV96xoyrT4kGJa8eaF8Sg/cHaEN9WIr/ixe9/8+7QxD0bsgITDgqPWMu2refChXIi28mApIiNzOYX7B826MEB76EtStC+SbLuSFkOiZZoaos/ewcUdYh9AodJ1gX8ovubekhO2Ms6hk8pcdMYlM+KmBPLIUWscoy1UF7Q4hjpEpopTMsq9cKYo6NCIacjV0MiY1hRELgImRQqKUBYiC5VQ4aEatqZ2MljWvGQYP6C9LAz5CirvBA+UUyYndXmSPGQ2GDjwG0fZ4hQPPh21xH2l5BP+oCoD7UrHtITIvgej3CGnIkWhAN9fELfLggh7YrNyLIQp92sWUOOq31QxG92QAABBBBAvBNy658GMgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAokyI1xwQ7B1HNo+LGqt3HKItabzjWHOJfgM5tA7UOw6xeQAAAABJRU5ErkJggg==", + "description": "Simple form to input new string value for pre-defined server-side attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -280,6 +315,8 @@ { "alias": "update_location_timeseries", "name": "Update location timeseries", + "image": "data:image/png;base64,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", + "description": "Simple form to input new location for pre-defined timeseries keys.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -296,6 +333,8 @@ { "alias": "update_shared_double_attribute", "name": "Update shared double attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANMSURBVHja7d3fS5NRGAfw/SsrhSCKKIugburCi6KCLrropi4izME0c1kLUxquHybmKNPAi4qMJkm9hOkswhVkpaLMfhmlbjHYu+3Vaf7a3r3fLjad5nZh6NiZ3+diO+fAe/HhOc85Z+/Fjg4R2T0ieLjlMHQRT0iF4KGGPBGdHEIWREjWudVsgKhu3QiyIkYIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQghZS8jbbwDQOT9slJc/5HGJAOmoAaAUjKWERCO9z7RI5kPGC8cBxy3A5/wUBoyy1qNirg+I9jtlAPBcvGkt7xWgRmyvgKpu9JR3NF4HjHLk9DSCBmi2eqnsFwCEK8vGRSj2j9fgM85h0Ito4VgC0m/R4GwEgHZ72xMRIOHiwIv7wJ/HN6oKfAmIdL62tsoCAMps1CvE8vuwvWIIaHgehTEGmULQgPYGv9+vCLWP/Cg1a4ClG18LvDDKKPmJPgNGzwXgdQkF0S5JAPrPXqgzDcAo402R2WYAukorzUMi7uzR6YWqmY0BJzQeUQghhBBC1h9kZllDDIj2cumBaKZaijWk6hmRIFqLybl0RDJJi75EgWitJse/Y20mKf4hDkRrWe4AJJOUPB8ZC0mWj1hOUjgyDjJ9zw1Ae5rcsUKIbK9rmVzovatv+hJvfh5ec4hirfidYl6teGp1bd1/Km9XfDhasOnksZxGAIGmA/rWtU/J2NXL7tT5WEmxz+0sUqHsNsR69lwXcDc3AC13b0U6IFCsZanzsZLld1D/AYApP/5wDYBBfTfwPupPCwSKNbnj/zbE44WLOvYcLwCkCYLpVTyiODb2zDe/P7qypQnphKzioXF0myUxMw9t3+MUFBLcd2LxP1qo1bnDQkKmjhydWjqw4YGIkMiJ/MT5ufggAL9eEhCilWzudLlcLgWvbYBDfyfoPpOnCAiZ1MeiGeYdKtCcp9cfHhB11YoXeewFo2+Cv9kJIYQQQghZf5DbaQ1mhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCEkMyFZc0FwdlzZPC7rwtlxibaqy45rzVX8BUWnY6Q1/jMIAAAAAElFTkSuQmCC", + "description": "Simple form to input new double value for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -312,6 +351,8 @@ { "alias": "update_shared_integer_attribute", "name": "Update shared integer attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAANBSURBVHja7d3dS1NhHAfw/Sum1IUQBZYJIdSF4VVQIOSNXZiJg5nmWi18CXG9WJgSqd1IBUWu10PY1EBcIFYqyoTCLOZWA8/0uGmbbjs73y7mS+Z2YdjYs76/i3Oe88C5+PB73jhwnkeHiOxyCh4uOQxdxO1XIXiofndEJ/uRBuGXdS41HSCqS+dEWoSTEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYSQfwl59xkA+teqDfLWl9wOESC9twAo5QsJIdHI6EstkvoQX4UP6LsNzNo/hgGDrI2oCI0B0XG7DADuSzcttaMC9JG2t0DTMEZqezuvAwY5UhbEvB5a213J9A0Awg0mnwid/cM1zBpCmPQgWrGwARlv1GDvBABbd88TESDhc3Ov7wM/H99oKp/dgEgXWlqaGgFAWYl6hBh+H9rqp4COV1EYYpAA5vWwdXi9XkWoeeRLjVkDGofxqdwDg4zqrxjTY+b8HDwOoSDaZQnAeNXFVuMEDDIGKs1temCwpsE8JeLMHg2u95qVGHBR4xKFEEIIIeT/gyxvKYgB0d5sXhAtN0uxgtS8LBJEsxrtm2sko/TbTRSI9tzY92ddj1FavYgD0axbHYBklOLnI2Uh8fIRy0kCR8pBgvdcALRn8R3bhMjdrdYlAFh62mpd/Uk81J8ciGKp/56gXW27aQ3uPVqac9AJTOUePpOfMw1gom5/VpJSsnC1zpU4H9vp7KEDlSqUQ3qg6GQI4cKzwIvMU6eTBYFiMSXOx3aG38mM9wCMBUDDAIAr+YDzB9qTBoFiie/4uwmxuGJ1+CgqAYBkQhDcwSVKX+YIANi6SvKmkw7ZwUXjzL5GAEDlsT2l8wJD5o+UrO1o4S0sFhcSOH4isP7wYFdAVEikpEABgGBWF4D27KigEK06u9/hcDgUlOUNLQ7lmkRtWksZsXiEhaqsjN3mgMij1lqEPcJsY8OPD4QQQgghhOws5E5SgxkhhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCElNSNocEJweRzb7ZF04PQ7RVnXpcay5il+KVmW7YpZ2rQAAAABJRU5ErkJggg==", + "description": "Simple form to input new integer value for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -328,6 +369,8 @@ { "alias": "update_shared_string_attribute", "name": "Update shared string attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAOkSURBVHja7d3bSxRRHAfw/VdG6yklCbPsRk+GhVLYDSXMohC1tczVUkxFXDULzW4a+SA9VIZRm5haIJqBmMqW2cUoddUVR1sved2dnV/nuJaX3X0Q1pjZvj/YM4fDmYfP/uZcdvbhaMgmmnpVHibRShpb/4REKg9pot+mESfIC2JC1Jgkb4BIJk0veUX0AgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAsp6QN194+epPs1Z0vqm/Uw2QuuussMSOuYXYbe3PZJvyIeNx40T1xUTDTe+sHCK3STTfwQDGpgVU/+Vr+ox2FYyRktdEuS3UllFXVsAhtrMz9DOe5JI7htQfvIM1K3VcDYO9NZ+GtfP00Uz2uLEliDFHpqYy3qG2suaxGiDW86PVFURTD6/mxg4vQQwpRUW5ObyDZc5uVsX0+6A2s5uo9LmdtA7INIfUlo6MjFhUtY58S06TiXJa6HOsmc9aSd+pI576Lo6SuVNVEDndwErjhUs3dO85pCExrSSeqDE5K61bjSu7febvqJlzACdlbFEAAQQQQP4/yKxTRR0Q+eXKDdFsocFRMRTOqgkiP9E1rWwx6AzLLmqByE919avbajihxqVDsRCWj3rnVpYM1/lQLMRVPhw5ceNQHGTmnok7qlw7PAuJOrWOEIs+c8DNc+XxR2tdITSWd8XkPh8eHezrC2E5SXWfjzVNvxUdRn0Bf29V3sZvq2GVD82FuUYy5ukbFiBdN7MbeU+pOvvuV96xoyrT4kGJa8eaF8Sg/cHaEN9WIr/ixe9/8+7QxD0bsgITDgqPWMu2refChXIi28mApIiNzOYX7B826MEB76EtStC+SbLuSFkOiZZoaos/ewcUdYh9AodJ1gX8ovubekhO2Ms6hk8pcdMYlM+KmBPLIUWscoy1UF7Q4hjpEpopTMsq9cKYo6NCIacjV0MiY1hRELgImRQqKUBYiC5VQ4aEatqZ2MljWvGQYP6C9LAz5CirvBA+UUyYndXmSPGQ2GDjwG0fZ4hQPPh21xH2l5BP+oCoD7UrHtITIvgej3CGnIkWhAN9fELfLggh7YrNyLIQp92sWUOOq31QxG92QAABBBBAvBNy658GMgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAokyI1xwQ7B1HNo+LGqt3HKItabzjWHOJfgM5tA7UOw6xeQAAAABJRU5ErkJggg==", + "description": "Simple form to input new string value for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -344,6 +387,8 @@ { "alias": "update_server_location_attribute", "name": "Update server location attribute", + "image": "data:image/png;base64,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", + "description": "Simple form to input new location for pre-defined server attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -360,6 +405,8 @@ { "alias": "markers_placement_google_maps", "name": "Markers Placement - Google Maps", + "image": "data:image/png;base64,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", + "description": "Allows configuring location of the selected entities on the Google Map.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -376,6 +423,8 @@ { "alias": "update_shared_location_attribute", "name": "Update shared location attribute", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAlUSURBVHja7d3tVxNXHsBx/pULgUqjFbFI7bZL1bXao6ltPW23dmuXbs1a3QKuUiTFiniKUpQHW42KWh8wylqLC0ZROIuAS9VKlUaCUMuTLRCIIOEpkGS++yJREc2+2D3SCefeF3DnNxngc+beuXcu+WWCcHW1tQR4aesaJch1u89NgBd3321XUFcfk6D0dQW1uScDxN0W1MKkKC0SIiESIiESIiESIiESIiESIiESIiESIiES8j9BlJOD4yIlneMCDv3YLdsP6oR49PYxW6dLobyb8kJ/EGXkpgmXmiH2i5ecNO4w3sLa15yXXc+NfrjmRLle9aseFEtFO4A9OWtzapWKIdZPzx5MdX2fkVVDmrU2Z8t3bPgJ4m18/Xlhuh7yck8n1wF4tqztUnPTamiFxDYKTkGaFXM+Pogtfhibnp8MHq5mA1R+XXFAzRDnN5mfr7r1KOT6VnDoKVubnb01GcAxoPyiZojJ5CLlIchnjRBvs24Gh56q7O7ubruqL78+SG4pLR83cqIA0qyU7IftFbR/ZBtMaOYHPbY17dhrVA6JT0hI6GxYm5yx4SLWjw+RZqUlbic31qRkJti4uiZltx6ufJK63hIQI7vHOy4OjwIw4gS3N+IZ9o0mHjlFkRAJkRAJkRAJkRAJkZCAh5hNJpPJ5IHhk9sPddwP/3hgV7kC8NPBnf9SfMFqE0C5yWQymQYBhi6WqAYS/XRERESEG8fLEbEvTq31RTdr3n5P84EHDmve+EC73Psur5anhQLopkRERER0AEZtyJtqgbg1//RWcrRtDC/x/V1V4gyUCjPtoUawhpwCUJZphQJE7/Ud+1WYcUg1TatDXPZW3lwNHA12ApAZBaDNocFgB6K3AhyPzBAKuDVF3iO6w3arqI/UiuqTZhsQsxWoEK0Pdt0JKfZhNQVA5/Rv84QCHeJMYdFtIP+pjjOmRrVAzovQaM2UUpi6A6gRY5Z2/x4zAvSbvvr9ilHgr8vJEwrUCk10qOY4JEdGLIzR5KsE0prbhn1J9BBaL+TBWokp9BJAuy4mzKiAWdvmhXTtuEn/+9pu9CHnUFLCO1Q0jlSLGiJygKui7l6sPPToverF0Hx6Z+3DCwHgljhL7NtApyhSEaRVnGfBZ0CJ6PaFrmlzH+z/4weki0U63WyhM3ojTpHP+kWAO+SwOiCrYoEycZOVOiBjum/V5+fIVABOiU5At5Zas9lsXitO32DTIsAiKjj4VCdYRaU6IGUh+T0//mGpQpkw2i9MT4Xrm13YXnyt1mKxWNxdkX9pubMv+IL3xXlCgWua3DuNr88ZoS/6nVuNb7zsUknTyp8hQt5sA4xaoVk9CHvCbBQIb7nLDzohov7BGAjFzwmx2ArULxTBrzailj6idPiG59FWBwDjRmuH7ZFDbAO+SqdDzn4lREIkREIkZDJAvprQIs+IhKigDD9SCQyIcrbnYUem786zKHM4kCDKicRxN2hFiUVjvgUKRPk28fz42JnEIt+XwIEoJx51QFFi0ePPh2ohjzsf3nPix6E6yNDeNkA5+XhHAEF60jf+4qddBVjT6t3yWZv/8xFInb0nPcn/+Qioy29P+uMdgTcgDk2yKYqc/UqISiGPpl2M2TV+YbfJ8NDWTTVBHk67eLiYBzhZ4Q/icl88p7jVB3FUXuqH5u76ChvQUNFpHeSa82bWviZqRnFdVRi98p3VAK6aSjvAreSMjJQ61UHaEwuOJXWxP+WYKd5BaXJhVlwLcV2X0nOv81EvA3qPsi3r5EYD7sy8U0m/AgynGAbU17T2nIbiA+z/BtJrPXEtuNa0ENfFobP4IHUpHuoMfLcNzh0GKCz+tkh9kJQGaEhlfylkXelZpUDSOEjZPmgyULA+OzttG0C3a7RTfZD0G1C72QcZXOkeC1nVw4DeU2mEJgOFB7u7u3tVePn1QYp3eTw7T/kgpFXRvrqFuC7yT0FyHXV6T2eCnfMGGpL6aK1XJSQ+ISGhc2Rf8vq9I/cgzUkpXyS0EtfF9b8d49/xhi/1HsoSPjUa4Py6jRtaVAi5X0bHTDIHlUHnKu8nig2Pgsu7y+VNyfA4lICZohzdVbZNDSlh/zdEsZbWKpMBIme/EiIhEiIhEiIhEvJbQ2ym3KIR8JN28b3JW4ag/eiO0y6AwRPbjnQBOIt3HGkDcJdkH7YDKNW5eW0A1O7aef+25VzRBECqps1fETW3Bz9pF0adTqfTzQ7rp0y7YEXkgn7omRMZ+/z0evh1zswP500pgdFlT/9pdmQ9kKh5Z86UC8DO4KWLQ3zvgL4cHPPkIUp0vIfemVv8pV14y7J1uCLWK3Q/kwNbpncwuPB9WDGvD3dsFBwMtTCw+HW4IM7i/nDWCE0aI2ya0gHgnKudAEifwQLELveXduFtJsFW7IYG4O2VsHgdsC/MozyzAzghengvFigWHWx4CbCIavaHjkKvxgTwxfykmAnq7M4XNv7XtIvV7967dZyVAVG5QInwfapIzrMedOuARnGZj5cCIyEFbJ8J8GIGcCPs+w0TAzmRt2ThHf9pF9CiKQfAtGfRq3chZB9wUXhXfm9HGGHlYqBWlLA10gk9GiPHgjth9LkkcOtSmCDIW/M1hmH8pl1Aynzv7eKr8zSbRkF4IfUAztded0GJ2NV/bZ6owKr5tLfxLXEI+7TYzvbVYhMYZzsmCgJNUQb8p13Yw4/dizXM2Axhu4FK0Qy4V/6uEyArTMzIFDfh+FQR/qUogfJnhSbjGSPN4eeYOAhps/GfdrF95oMFlvVzfZ2pUAwCKTN/9sYdjc4j01zAUONAVXA74GrsvS2qWanR6XQzn9IVPmlIoygHkl7BT9oFDM7IBrguLgNxS+H9t4DUaCB36o/3f5Bjboq34lr2nu/K/kmMm2qz2WxeHmVuetIQ9yuvWO6WhBvxk3YBB6Z0A4zMXVLfWxR2EIqDD9wp1WbAseA9FovF0gaDlbujX+4F3NWH581qBqg5/ob2iu+XTEjTuv3nYKHd7uZB2sXeh9Iu3C8k+TrSu8Fi2pcKkBMuNGucsNj7mgSoCV+Q7QC4E/ZSmje5IfL5xJ+ZSAgMt/uak5+0i/tlqMO32DXS+tv9Z0RO4yVEQiREQiTkSUBk2oVsWhIiIRIiIRIiIRIiIRIiIRIiIRIiIRLyOMikeUDw5Hhk892uoNHJ8RBtd9DkeKy5m/8AUOf9PFgBBdoAAAAASUVORK5CYII=", + "description": "Simple form to input new location for pre-defined shared attribute key.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -392,6 +441,8 @@ { "alias": "web_camera_input", "name": "Photo camera input", + "image": "data:image/png;base64,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", + "description": "Simple form to take webcamera image or upload photo.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -402,7 +453,7 @@ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.photoCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Photo Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "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\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "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\":{},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } } ] diff --git a/application/src/main/data/json/system/widget_bundles/maps.json b/application/src/main/data/json/system/widget_bundles/maps.json index 7e895b7bdd..aaf958a646 100644 --- a/application/src/main/data/json/system/widget_bundles/maps.json +++ b/application/src/main/data/json/system/widget_bundles/maps.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "maps_v2", "title": "Maps", - "image": null + "image": "data:image/png;base64,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", + "description": "Visualize latest location or trip animation of the devices or other entities on the indoor or outdoor maps." }, "widgetTypes": [ { "alias": "route_map_tencent_maps", "name": "Route Map - Tencent Maps", + "image": "data:image/png;base64,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", + "description": "Trip animation on the Tencent maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", "descriptor": { "type": "timeseries", "sizeX": 8.5, @@ -24,6 +27,8 @@ { "alias": "test", "name": "Trip Animation", + "image": "data:image/png;base64,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", + "description": "Trip animation on the OpenStreetMap or other map providers. Allows to visualize location and other timeseries data for each point in time.", "descriptor": { "type": "timeseries", "sizeX": 10, @@ -40,6 +45,8 @@ { "alias": "route_map", "name": "Route Map", + "image": "data:image/png;base64,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", + "description": "TTrip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", "descriptor": { "type": "timeseries", "sizeX": 8.5, @@ -56,6 +63,8 @@ { "alias": "route_map_openstreetmap", "name": "Route Map - OpenStreetMap", + "image": "data:image/png;base64,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", + "description": "Trip animation on the OpenStreetMap. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", "descriptor": { "type": "timeseries", "sizeX": 8.5, @@ -72,6 +81,8 @@ { "alias": "tencent_maps", "name": "Tencent Maps", + "image": "data:image/png;base64,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", + "description": "Show latest values and location of the entities on Tencent maps.", "descriptor": { "type": "latest", "sizeX": 9, @@ -88,6 +99,8 @@ { "alias": "here_map", "name": "HERE Map", + "image": "data:image/png;base64,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", + "description": "Show latest values and location of the entities on HERE maps.", "descriptor": { "type": "latest", "sizeX": 9.5, @@ -104,6 +117,8 @@ { "alias": "image_map", "name": "Image Map", + "image": "data:image/png;base64,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", + "description": "Show latest values and location of the entities on image map. Uses configurable background image and coordintates in range from 0 to 1.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -120,6 +135,8 @@ { "alias": "google_maps", "name": "Google Maps", + "image": "data:image/png;base64,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", + "description": "Show latest values and location of the entities on Google Maps.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -136,6 +153,8 @@ { "alias": "openstreetmap", "name": "OpenStreetMap", + "image": "data:image/png;base64,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", + "description": "Show latest values and location of the entities on OpenStreetMap.", "descriptor": { "type": "latest", "sizeX": 8.5, @@ -150,4 +169,4 @@ } } ] -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/navigation_widgets.json b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json index c37e21906c..f7d0b303b4 100644 --- a/application/src/main/data/json/system/widget_bundles/navigation_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json @@ -2,12 +2,15 @@ "widgetsBundle": { "alias": "navigation_widgets", "title": "Navigation widgets", - "image": null + "image": "data:image/png;base64,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", + "description": "Useful to define home dashboard of the user. Contains widgets that enable navigation to other dashboards and menu items." }, "widgetTypes": [ { "alias": "navigation_cards", "name": "Navigation cards", + "image": "data:image/png;base64,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", + "description": "Allows to open one or more specific pages using filter defined in the advanced settings of the widget.", "descriptor": { "type": "static", "sizeX": 7, @@ -16,7 +19,7 @@ "templateHtml": "", "templateCss": "/*#widget-container {\n overflow-y: auto;\n box-sizing: content-box !important;\n cursor: auto;\n}*/\n\n#widget-container #container {\n overflow-y: auto;\n box-sizing: content-box;\n cursor: auto;\n}", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onResize = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"filterType\": {\n \"title\": \"Filter type\",\n \"type\": \"string\",\n \"default\": \"all\"\n },\n \"filter\": {\n \"title\": \"Items\",\n \"type\": \"array\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"filterType\",\n \"type\": \"radios\",\n \"direction\": \"row\",\n \"titleMap\": [\n {\n \"value\": \"all\",\n \"name\": \"All items\"\n },\n {\n \"value\": \"include\",\n \"name\": \"Include items\"\n },\n {\n \"value\": \"exclude\",\n \"name\": \"Exclude items\"\n }\n ]\n },\n {\n \"key\": \"filter\",\n \"type\": \"rc-select\",\n \"condition\": \"model.filterType !== 'all'\",\n \"tags\": true,\n \"placeholder\": \"Enter urls to filter\",\n \"items\": [{\"value\": \"/devices\", \"label\": \"/devices\"}, {\"value\": \"/assets\", \"label\": \"/assets\"}, {\"value\": \"/deviceProfies\", \"label\": \"/deviceProfies\"}]\n }\n ]\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"filterType\": {\n \"title\": \"Filter type\",\n \"type\": \"string\",\n \"default\": \"all\"\n },\n \"filter\": {\n \"title\": \"Items\",\n \"type\": \"array\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"filterType\",\n \"type\": \"radios\",\n \"direction\": \"row\",\n \"titleMap\": [\n {\n \"value\": \"all\",\n \"name\": \"All items\"\n },\n {\n \"value\": \"include\",\n \"name\": \"Include items\"\n },\n {\n \"value\": \"exclude\",\n \"name\": \"Exclude items\"\n }\n ]\n },\n {\n \"key\": \"filter\",\n \"type\": \"rc-select\",\n \"condition\": \"model.filterType !== 'all'\",\n \"tags\": true,\n \"placeholder\": \"Enter urls to filter\",\n \"items\": [{\"value\": \"/devices\", \"label\": \"/devices\"}, {\"value\": \"/assets\", \"label\": \"/assets\"}, {\"value\": \"/deviceProfiles\", \"label\": \"/deviceProfiles\"}]\n }\n ]\n}\n", "dataKeySettingsSchema": "{}\n", "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\":\"rgba(255,255,255,0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"filterType\":\"all\"},\"title\":\"Navigation cards\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" } @@ -24,6 +27,8 @@ { "alias": "navigation_card", "name": "Navigation card", + "image": "data:image/png;base64,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", + "description": "Allows to open specific page using relative path defined in the advanced settings of the widget.", "descriptor": { "type": "static", "sizeX": 2.5, @@ -38,4 +43,4 @@ } } ] -} \ No newline at end of file +} diff --git a/application/src/main/data/upgrade/3.2.1/schema_update.sql b/application/src/main/data/upgrade/3.2.1/schema_update.sql new file mode 100644 index 0000000000..879c2f8f4c --- /dev/null +++ b/application/src/main/data/upgrade/3.2.1/schema_update.sql @@ -0,0 +1,23 @@ +-- +-- Copyright © 2016-2021 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +ALTER TABLE widget_type + ADD COLUMN IF NOT EXISTS image varchar (1000000), + ADD COLUMN IF NOT EXISTS description varchar (255); + +ALTER TABLE widgets_bundle + ADD COLUMN IF NOT EXISTS image varchar (1000000), + ADD COLUMN IF NOT EXISTS description varchar (255); diff --git a/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql b/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql new file mode 100644 index 0000000000..326ca27715 --- /dev/null +++ b/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql @@ -0,0 +1,87 @@ +-- +-- Copyright © 2016-2021 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, + IN system_ttl bigint, INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE +tenant_cursor CURSOR FOR select tenant.id as tenant_id + from tenant; + tenant_id_record uuid; + customer_id_record uuid; + tenant_ttl bigint; + customer_ttl bigint; + deleted_for_entities bigint; + tenant_ttl_ts bigint; + customer_ttl_ts bigint; +BEGIN +OPEN tenant_cursor; +FETCH tenant_cursor INTO tenant_id_record; +WHILE FOUND + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + tenant_id_record, 'TTL') INTO tenant_ttl; + if tenant_ttl IS NULL THEN + tenant_ttl := system_ttl; +END IF; + IF tenant_ttl > 0 THEN + tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; + deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; +END IF; +FOR customer_id_record IN +SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + customer_id_record, 'TTL') INTO customer_ttl; +IF customer_ttl IS NULL THEN + customer_ttl_ts := tenant_ttl_ts; +ELSE + IF customer_ttl > 0 THEN + customer_ttl_ts := + (EXTRACT(EPOCH FROM current_timestamp) * 1000 - + customer_ttl::bigint * 1000)::bigint; +END IF; +END IF; + IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN + deleted_for_entities := + delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; + deleted_for_entities := + delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, + customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; +END IF; +END LOOP; +FETCH tenant_cursor INTO tenant_id_record; +END LOOP; +END +$$; 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 590138dbd4..ca7673fc91 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -86,6 +86,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -619,13 +620,13 @@ public abstract class BaseController { } } - WidgetType checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { + WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { try { validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); - WidgetType widgetType = widgetTypeService.findWidgetTypeById(getCurrentUser().getTenantId(), widgetTypeId); - checkNotNull(widgetType); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetType); - return widgetType; + WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); + checkNotNull(widgetTypeDetails); + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); + return widgetTypeDetails; } catch (Exception e) { throw handleException(e, false); } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 3d31ba6c0c..6591a1577a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -19,11 +19,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +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.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; -import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; import java.util.List; @RestController @@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController { @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { + if (log.isDebugEnabled()) { + log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + log.debug("Header: {} {}", header, request.getHeader(header)); + } + } return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java index 269755bd3e..f98bf364df 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -97,9 +97,13 @@ public class RpcController extends BaseController { private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { try { JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); - RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), - jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); - + String requestData; + if (rpcRequestBody.get("params").isTextual()) { + requestData = rpcRequestBody.get("params").asText(); + } else { + requestData = jsonMapper.writeValueAsString(rpcRequestBody.get("params")); + } + RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), requestData); if (rpcRequestBody.has("timeout")) { cmd.setTimeout(rpcRequestBody.get("timeout").asLong()); } 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 d15cb42e72..85ec0950e3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; 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 lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -457,6 +458,25 @@ public class RuleChainController extends BaseController { } private String msgToOutput(TbMsg msg) throws Exception { + JsonNode resultNode = convertMsgToOut(msg); + return objectMapper.writeValueAsString(resultNode); + } + + private String msgToOutput(List msgs) throws Exception { + JsonNode resultNode; + if (msgs.size() > 1) { + resultNode = objectMapper.createArrayNode(); + for (TbMsg msg : msgs) { + JsonNode convertedData = convertMsgToOut(msg); + ((ArrayNode) resultNode).add(convertedData); + } + } else { + resultNode = convertMsgToOut(msgs.get(0)); + } + return objectMapper.writeValueAsString(resultNode); + } + + private JsonNode convertMsgToOut(TbMsg msg) throws Exception{ ObjectNode msgData = objectMapper.createObjectNode(); if (!StringUtils.isEmpty(msg.getData())) { msgData.set("msg", objectMapper.readTree(msg.getData())); @@ -464,7 +484,7 @@ public class RuleChainController extends BaseController { Map metadata = msg.getMetaData().getData(); msgData.set("metadata", objectMapper.valueToTree(metadata)); msgData.put("msgType", msg.getType()); - return objectMapper.writeValueAsString(msgData); + return msgData; } @PreAuthorize("hasAuthority('TENANT_ADMIN')") @@ -622,4 +642,5 @@ public class RuleChainController extends BaseController { throw handleException(e); } } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index f9eda524e1..7a16b0d7fc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -49,7 +51,7 @@ public class WidgetTypeController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/widgetType/{widgetTypeId}", method = RequestMethod.GET) @ResponseBody - public WidgetType getWidgetTypeById(@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { + public WidgetTypeDetails getWidgetTypeById(@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); try { WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); @@ -62,21 +64,21 @@ public class WidgetTypeController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/widgetType", method = RequestMethod.POST) @ResponseBody - public WidgetType saveWidgetType(@RequestBody WidgetType widgetType) throws ThingsboardException { + public WidgetTypeDetails saveWidgetType(@RequestBody WidgetTypeDetails widgetTypeDetails) throws ThingsboardException { try { if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - widgetType.setTenantId(TenantId.SYS_TENANT_ID); + widgetTypeDetails.setTenantId(TenantId.SYS_TENANT_ID); } else { - widgetType.setTenantId(getCurrentUser().getTenantId()); + widgetTypeDetails.setTenantId(getCurrentUser().getTenantId()); } - checkEntity(widgetType.getId(), widgetType, Resource.WIDGET_TYPE); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE); + WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); sendEntityNotificationMsg(getTenantId(), savedWidgetType.getId(), - widgetType.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); + widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); - return checkNotNull(savedWidgetType); + return checkNotNull(widgetTypeDetails); } catch (Exception e) { throw handleException(e); } @@ -118,6 +120,44 @@ public class WidgetTypeController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/widgetTypesDetails", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET) + @ResponseBody + public List getBundleWidgetTypesDetails( + @RequestParam boolean isSystem, + @RequestParam String bundleAlias) throws ThingsboardException { + try { + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.SYS_TENANT_ID; + } else { + tenantId = getCurrentUser().getTenantId(); + } + return checkNotNull(widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId, bundleAlias)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/widgetTypesInfos", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET) + @ResponseBody + public List getBundleWidgetTypesInfos( + @RequestParam boolean isSystem, + @RequestParam String bundleAlias) throws ThingsboardException { + try { + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.SYS_TENANT_ID; + } else { + tenantId = getCurrentUser().getTenantId(); + } + return checkNotNull(widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId, bundleAlias)); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/widgetType", params = {"isSystem", "bundleAlias", "alias"}, method = RequestMethod.GET) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 166403f417..0e59030fe2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -63,6 +63,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr private static final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>(); private static final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); + private static final ByteBuffer PING_MSG = ByteBuffer.wrap(new byte[]{}); @Autowired private TelemetryWebSocketService webSocketService; @@ -212,7 +213,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr synchronized void sendPing(long currentTime) { try { if (currentTime - lastActivityTime >= pingTimeout) { - this.asyncRemote.sendPing(ByteBuffer.wrap(new byte[]{})); + this.asyncRemote.sendPing(PING_MSG); lastActivityTime = currentTime; } } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 7bcd54632c..d90b3ede59 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -190,7 +190,7 @@ public class ThingsboardInstallService { if (databaseTsUpgradeService != null) { databaseTsUpgradeService.upgradeDatabase("3.2.1"); } - + databaseEntitiesUpgradeService.upgradeDatabase("3.2.1"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index d0a3984660..c2549803e5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -72,6 +72,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -421,20 +422,33 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); - for (Tenant tenant : tenantIterator) { - if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { - log.debug("[{}] Initializing tenant state.", tenant.getId()); - updateLock.lock(); - try { - updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); - log.debug("[{}] Initialized tenant state.", tenant.getId()); - } catch (Exception e) { - log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); - } finally { - updateLock.unlock(); + updateLock.lock(); + try { + ExecutorService tmpInitExecutor = Executors.newWorkStealingPool(20); + try { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); + List> futures = new ArrayList<>(); + for (Tenant tenant : tenantIterator) { + if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + log.debug("[{}] Initializing tenant state.", tenant.getId()); + futures.add(tmpInitExecutor.submit(() -> { + try { + updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + log.debug("[{}] Initialized tenant state.", tenant.getId()); + } catch (Exception e) { + log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); + } + })); + } + } + for (Future future : futures) { + future.get(); } + } finally { + tmpInitExecutor.shutdownNow(); } + } finally { + updateLock.unlock(); } log.info("Initialized tenant states."); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java index 343d51c64c..fba21ed1ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.edge.UpdateMsgType; import org.thingsboard.server.gen.edge.WidgetsBundleUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.nio.charset.StandardCharsets; + @Component @TbCoreComponent public class WidgetsBundleMsgConstructor { @@ -36,7 +38,7 @@ public class WidgetsBundleMsgConstructor { .setTitle(widgetsBundle.getTitle()) .setAlias(widgetsBundle.getAlias()); if (widgetsBundle.getImage() != null) { - builder.setImage(ByteString.copyFrom(widgetsBundle.getImage())); + builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); } if (widgetsBundle.getTenantId().equals(TenantId.SYS_TENANT_ID)) { builder.setIsSystem(true); diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java index 1a0ad6cc7f..5f8b961a46 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java @@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { @Autowired protected InstallScripts installScripts; - protected abstract void loadSql(Connection conn, String version, String fileName); + protected abstract void loadSql(Connection conn, String fileName, String version); protected void loadFunctions(Path sqlFile, Connection conn) throws Exception { String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 7cd3e0d56a..967f759372 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplat import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; @@ -197,9 +198,9 @@ public class InstallScripts { widgetTypesArrayJson.forEach( widgetTypeJson -> { try { - WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class); - widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); - widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails widgetTypeDetails = objectMapper.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + widgetTypeDetails.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetTypeService.saveWidgetType(widgetTypeDetails); } catch (Exception e) { log.error("Unable to load widget type from json: [{}]", path.toString()); throw new RuntimeException("Unable to load widget type from json", e); diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index 53d1f52989..051333e953 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe log.info("PostgreSQL version is valid!"); if (isOldSchema(conn, 2004003)) { log.info("Load upgrade functions ..."); - loadSql(conn, "2.4.3", LOAD_FUNCTIONS_SQL); + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); log.info("Updating timeseries schema ..."); executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); if (!partitionType.equals("INDEFINITE")) { @@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe } log.info("Load TTL functions ..."); - loadSql(conn, "2.4.3", LOAD_TTL_FUNCTIONS_SQL); + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); log.info("Load Drop Partitions functions ..."); - loadSql(conn, "2.4.3", LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); @@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe case "3.2.1": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Load TTL functions ..."); - loadSql(conn, "2.4.3", LOAD_TTL_FUNCTIONS_SQL); + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); log.info("Load Drop Partitions functions ..."); - loadSql(conn, "2.4.3", LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);"); executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);"); @@ -212,7 +212,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe case "3.2.2": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Load Edge TTL functions ..."); - loadSql(conn, "3.2.2", LOAD_TTL_FUNCTIONS_SQL); + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.2"); } break; default: @@ -250,7 +250,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe } @Override - protected void loadSql(Connection conn, String version, String fileName) { + protected void loadSql(Connection conn, String fileName, String version) { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName); try { loadFunctions(schemaUpdateFile, conn); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 1bb66c4a40..f66e6e432e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -434,6 +434,18 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.info("Schema updated."); } break; + case "3.2.1": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time);"); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.1", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002002;"); + log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; case "3.2.2": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); @@ -442,7 +454,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService loadSql(schemaUpdateFile, conn); try { conn.createStatement().execute("ALTER TABLE rule_chain ADD COLUMN type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) {} + } catch (Exception e) { + } conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource (" + " tenant_id uuid NOT NULL," + diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 8c6ec59f12..417e3f8f1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr log.info("PostgreSQL version is valid!"); if (isOldSchema(conn, 2004003)) { log.info("Load upgrade functions ..."); - loadSql(conn, "2.4.3", LOAD_FUNCTIONS_SQL); + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); log.info("Updating timescale schema ..."); executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); @@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr } log.info("Load TTL functions ..."); - loadSql(conn, "2.4.3", LOAD_TTL_FUNCTIONS_SQL); + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); log.info("schema timescale updated!"); @@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr } break; case "3.1.1": + break; case "3.2.1": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1"); + } break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); @@ -201,7 +205,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr } @Override - protected void loadSql(Connection conn, String version, String fileName) { + protected void loadSql(Connection conn, String fileName, String version) { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName); try { loadFunctions(schemaUpdateFile, conn); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 00b4def397..cf4aa21254 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -329,10 +329,12 @@ public class DefaultTbClusterService implements TbClusterService { byte[] msgBytes = encodingService.encode(msg); TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); - if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) - || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE) - || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) - || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE) + EntityType entityType = msg.getEntityId().getEntityType(); + if (entityType.equals(EntityType.TENANT) + || entityType.equals(EntityType.TENANT_PROFILE) + || entityType.equals(EntityType.DEVICE_PROFILE) + || entityType.equals(EntityType.API_USAGE_STATE) + || (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED) || msg.getEntityId().getEntityType().equals(EntityType.EDGE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 1d7a5d9533..f581145907 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -40,6 +40,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { @@ -56,6 +57,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer private final FutureCallback evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); private final FutureCallback invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); + private final ReentrantLock evalLock = new ReentrantLock(); + @Getter private final JsExecutorService jsExecutor; @@ -121,10 +124,15 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer jsPushedMsgs.incrementAndGet(); ListenableFuture result = jsExecutor.executeAsync(() -> { try { - if (useJsSandbox()) { - sandbox.eval(jsScript); - } else { - engine.eval(jsScript); + evalLock.lock(); + try { + if (useJsSandbox()) { + sandbox.eval(jsScript); + } else { + engine.eval(jsScript); + } + } finally { + evalLock.unlock(); } scriptIdToNameMap.put(scriptId, functionName); return scriptId; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 35f38b110e..5dffc0217c 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -108,13 +108,18 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public TbMsg executeUpdate(TbMsg msg) throws ScriptException { + public List executeUpdate(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); - if (!result.isObject()) { + if (result.isObject()) { + return Collections.singletonList(unbindMsg(result, msg)); + } else if (result.isArray()){ + List res = new ArrayList<>(result.size()); + result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); + return res; + } else { log.warn("Wrong result type: {}", result.getNodeType()); throw new ScriptException("Wrong result type: " + result.getNodeType()); } - return unbindMsg(result, msg); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index 8067562279..fc1705d688 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -45,7 +45,8 @@ public class CustomerUserPermissions extends AbstractPermissions { private static final PermissionChecker customerEntityPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS, - Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES) { + Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES, + Operation.WRITE, Operation.WRITE_ATTRIBUTES, Operation.WRITE_TELEMETRY) { @Override @SuppressWarnings("unchecked") diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index ce8838aaa8..d6f21b380b 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -213,7 +213,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, s -> true, s -> { List subscriptionUpdate = null; for (TsKvEntry kv : ts) { - if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) { + if ((s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) { if (subscriptionUpdate == null) { subscriptionUpdate = new ArrayList<>(); } @@ -375,11 +375,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } } - private boolean isInTimeRange(TbTimeseriesSubscription subscription, long kvTime) { - return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime) - && (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime); - } - private void removeSubscriptionFromEntityMap(TbSubscription sub) { Set entitySubSet = subscriptionsByEntityId.get(sub.getEntityId()); if (entitySubSet != null) { @@ -429,16 +424,9 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId()); long curTs = System.currentTimeMillis(); - List queries = new ArrayList<>(); - subscription.getKeyStates().forEach((key, value) -> { - if (curTs > value) { - long startTs = subscription.getStartTime() > 0 ? Math.max(subscription.getStartTime(), value + 1L) : (value + 1L); - long endTs = subscription.getEndTime() > 0 ? Math.min(subscription.getEndTime(), curTs) : curTs; - queries.add(new BaseReadTsKvQuery(key, startTs, endTs, 0, 1000, Aggregation.NONE)); - } - }); - if (!queries.isEmpty()) { - DonAsynchron.withCallback(tsService.findAll(subscription.getTenantId(), subscription.getEntityId(), queries), + + if (subscription.isLatestValues()) { + DonAsynchron.withCallback(tsService.findLatest(subscription.getTenantId(), subscription.getEntityId(), subscription.getKeyStates().keySet()), missedUpdates -> { if (missedUpdates != null && !missedUpdates.isEmpty()) { TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); @@ -447,6 +435,26 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor); + } else { + List queries = new ArrayList<>(); + subscription.getKeyStates().forEach((key, value) -> { + if (curTs > value) { + long startTs = subscription.getStartTime() > 0 ? Math.max(subscription.getStartTime(), value + 1L) : (value + 1L); + long endTs = subscription.getEndTime() > 0 ? Math.min(subscription.getEndTime(), curTs) : curTs; + queries.add(new BaseReadTsKvQuery(key, startTs, endTs, 0, 1000, Aggregation.NONE)); + } + }); + if (!queries.isEmpty()) { + DonAsynchron.withCallback(tsService.findAll(subscription.getTenantId(), subscription.getEntityId(), queries), + missedUpdates -> { + if (missedUpdates != null && !missedUpdates.isEmpty()) { + TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); + toCoreNotificationsProducer.send(tpi, toProto(subscription, missedUpdates), null); + } + }, + e -> log.error("Failed to fetch missed updates.", e), + tsCallBackExecutor); + } } } @@ -468,12 +476,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene data.forEach((key, value) -> { TbSubscriptionUpdateValueListProto.Builder dataBuilder = TbSubscriptionUpdateValueListProto.newBuilder(); dataBuilder.setKey(key); - value.forEach(v -> { + boolean hasData = false; + for (Object v : value) { Object[] array = (Object[]) v; dataBuilder.addTs((long) array[0]); - dataBuilder.addValue((String) array[1]); - }); - builder.addData(dataBuilder.build()); + String strVal = (String) array[1]; + if (strVal != null) { + hasData = true; + dataBuilder.addValue(strVal); + } + } + if (hasData) { + builder.addData(dataBuilder.build()); + } }); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 330b096b78..127de752d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -215,7 +215,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } else { historyFuture = Futures.immediateFuture(ctx); } - Futures.addCallback(historyFuture, new FutureCallback() { + Futures.addCallback(historyFuture, new FutureCallback<>() { @Override public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) { if (cmd.getLatestCmd() != null) { @@ -278,7 +278,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc wsService.sendWsMsg(ctx.getSessionId(), update); } else { ctx.fetchAlarms(); - ctx.createSubscriptions(cmd.getQuery().getLatestValues(), true); + ctx.createLatestValuesSubscriptions(cmd.getQuery().getLatestValues()); if (adq.getPageLink().getTimeWindow() > 0) { TbAlarmDataSubCtx finalCtx = ctx; ScheduledFuture task = scheduler.scheduleWithFixedDelay( @@ -419,7 +419,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } wsService.sendWsMsg(ctx.getSessionId(), update); if (subscribe) { - ctx.createSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), false); + ctx.createTimeseriesSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), cmd.getStartTs(), cmd.getEndTs()); } ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear()); return ctx; @@ -468,7 +468,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); } wsService.sendWsMsg(ctx.getSessionId(), update); - ctx.createSubscriptions(latestCmd.getKeys(), true); + ctx.createLatestValuesSubscriptions(latestCmd.getKeys()); } @Override @@ -484,7 +484,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc wsService.sendWsMsg(ctx.getSessionId(), update); ctx.setInitialDataSent(true); } - ctx.createSubscriptions(latestCmd.getKeys(), true); + ctx.createLatestValuesSubscriptions(latestCmd.getKeys()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 08f7bfa1a6..e054c9436f 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -115,10 +115,18 @@ public abstract class TbAbstractDataSubCtx keys, boolean resultToLatestValues) { + public void createLatestValuesSubscriptions(List keys) { + createSubscriptions(keys, true, 0, 0); + } + + public void createTimeseriesSubscriptions(List keys, long startTs, long endTs) { + createSubscriptions(keys, false, startTs, endTs); + } + + private void createSubscriptions(List keys, boolean latestValues, long startTs, long endTs) { Map> keysByType = getEntityKeyByTypeMap(keys); for (EntityData entityData : data.getData()) { - List entitySubscriptions = addSubscriptions(entityData, keysByType, resultToLatestValues); + List entitySubscriptions = addSubscriptions(entityData, keysByType, latestValues, startTs, endTs); entitySubscriptions.forEach(localSubscriptionService::addSubscription); } } @@ -129,14 +137,14 @@ public abstract class TbAbstractDataSubCtx addSubscriptions(EntityData entityData, Map> keysByType, boolean resultToLatestValues) { + protected List addSubscriptions(EntityData entityData, Map> keysByType, boolean latestValues, long startTs, long endTs) { List subscriptionList = new ArrayList<>(); keysByType.forEach((keysType, keysList) -> { int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); subToEntityIdMap.put(subIdx, entityData.getEntityId()); switch (keysType) { case TIME_SERIES: - subscriptionList.add(createTsSub(entityData, subIdx, keysList, resultToLatestValues)); + subscriptionList.add(createTsSub(entityData, subIdx, keysList, latestValues, startTs, endTs)); break; case CLIENT_ATTRIBUTE: subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.CLIENT_SCOPE, keysList)); @@ -171,9 +179,9 @@ public abstract class TbAbstractDataSubCtx subKeys, boolean resultToLatestValues) { + private TbSubscription createTsSub(EntityData entityData, int subIdx, List subKeys, boolean latestValues, long startTs, long endTs) { Map keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys); - if (entityData.getTimeseries() != null) { + if (!latestValues && entityData.getTimeseries() != null) { entityData.getTimeseries().forEach((k, v) -> { long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts); @@ -187,9 +195,12 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) + .updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, latestValues)) .allKeys(false) .keyStates(keyStates) + .latestValues(latestValues) + .startTime(startTs) + .endTime(endTs) .build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 8af9ba2330..893d7ed91c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -132,8 +132,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { } @Override - public void createSubscriptions(List keys, boolean resultToLatestValues) { - super.createSubscriptions(keys, resultToLatestValues); + public void createLatestValuesSubscriptions(List keys) { + super.createLatestValuesSubscriptions(keys); createAlarmSubscriptions(); } @@ -282,7 +282,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { newSubsList.forEach( entity -> { log.trace("[{}][{}] Found new subscription for entity: {}", sessionRef.getSessionId(), cmdId, entity.getEntityId()); - subsToAdd.addAll(addSubscriptions(entity, keysByType, true)); + subsToAdd.addAll(addSubscriptions(entity, keysByType, true, 0, 0)); } ); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 98f5663c1b..7325fe7878 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -48,9 +48,6 @@ import java.util.stream.Collectors; @Slf4j public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { - @Getter - @Setter - private TimeSeriesCmd tsCmd; @Getter @Setter private boolean initialDataSent; @@ -137,7 +134,8 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { for (TsValue update : new ArrayList<>(updateList)) { if (update.getTs() < v.getTs()) { log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); - updateList.remove(update); + // Looks like this is redundant feature and our UI is ready to merge the updates. + //updateList.remove(update); } else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); updateList.remove(update); @@ -182,25 +180,18 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { subIdsToCancel.forEach(subToEntityIdMap::remove); List newSubsList = newDataMap.entrySet().stream().filter(entry -> !currentSubs.contains(entry.getKey())).map(Map.Entry::getValue).collect(Collectors.toList()); if (!newSubsList.isEmpty()) { - boolean resultToLatestValues; - List keys = null; - if (curTsCmd != null) { - resultToLatestValues = false; - keys = curTsCmd.getKeys().stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()); - } else if (latestValueCmd != null) { - resultToLatestValues = true; - keys = latestValueCmd.getKeys(); - } else { - resultToLatestValues = true; - } - if (keys != null && !keys.isEmpty()) { - Map> keysByType = getEntityKeyByTypeMap(keys); - newSubsList.forEach( - entity -> { - log.trace("[{}][{}] Found new subscription for entity: {}", sessionRef.getSessionId(), cmdId, entity.getEntityId()); - subsToAdd.addAll(addSubscriptions(entity, keysByType, resultToLatestValues)); - } - ); + // NOTE: We ignore the TS subscriptions for new entities here, because widgets will re-init it's content and will create new subscriptions. + if (curTsCmd == null && latestValueCmd != null) { + List keys = latestValueCmd.getKeys(); + if (keys != null && !keys.isEmpty()) { + Map> keysByType = getEntityKeyByTypeMap(keys); + newSubsList.forEach( + entity -> { + log.trace("[{}][{}] Found new subscription for entity: {}", sessionRef.getSessionId(), cmdId, entity.getEntityId()); + subsToAdd.addAll(addSubscriptions(entity, keysByType, true, 0, 0)); + } + ); + } } } wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription)); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index da84e8e4ea..978d7aaac3 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -83,6 +83,7 @@ public class TbSubscriptionUtils { TbSubscriptionKetStateProto.newBuilder().setKey(key).setTs(value).build())); tSubProto.setStartTime(tSub.getStartTime()); tSubProto.setEndTime(tSub.getEndTime()); + tSubProto.setLatestValues(tSub.isLatestValues()); msgBuilder.setTelemetrySub(tSubProto.build()); break; case ATTRIBUTES: @@ -146,6 +147,7 @@ public class TbSubscriptionUtils { telemetrySub.getKeyStatesList().forEach(ksProto -> keyStates.put(ksProto.getKey(), ksProto.getTs())); builder.startTime(telemetrySub.getStartTime()); builder.endTime(telemetrySub.getEndTime()); + builder.latestValues(telemetrySub.getLatestValues()); builder.keyStates(keyStates); return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java index b9f7a732cc..962889f741 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java @@ -34,16 +34,19 @@ public class TbTimeseriesSubscription extends TbSubscription updateConsumer, - boolean allKeys, Map keyStates, long startTime, long endTime) { + boolean allKeys, Map keyStates, long startTime, long endTime, boolean latestValues) { super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateConsumer); this.allKeys = allKeys; this.keyStates = keyStates; this.startTime = startTime; this.endTime = endTime; + this.latestValues = latestValues; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 107b97dbb3..c2ff292667 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -22,9 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -35,43 +33,22 @@ import org.thingsboard.server.common.data.id.AlarmId; 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.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.common.data.query.AlarmDataPageLink; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; /** * Created by ashvayka on 27.03.18. @@ -124,9 +101,15 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService @Override public ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { + ListenableFuture result = clearAlarmForResult(tenantId, alarmId, details, clearTs); + return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + } + + @Override + public ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { ListenableFuture result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs); Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); - return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + return result; } @Override diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6a104384de..973ec8220c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -110,7 +110,7 @@ security: # Enable/disable claiming devices, if false -> the device's [claimingAllowed] SERVER_SCOPE attribute must be set to [true] to allow claiming specific device allowClaimingByDefault: "${SECURITY_CLAIM_ALLOW_CLAIMING_BY_DEFAULT:true}" # Time allowed to claim the device in milliseconds - duration: "${SECURITY_CLAIM_DURATION:60000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value + duration: "${SECURITY_CLAIM_DURATION:86400000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value basic: enabled: "${SECURITY_BASIC_ENABLED:false}" oauth2: @@ -352,8 +352,8 @@ caffeine: timeToLiveInMinutes: 1440 maxSize: 0 claimDevices: - timeToLiveInMinutes: 1 - maxSize: 0 + timeToLiveInMinutes: 1440 + maxSize: 1000 securitySettings: timeToLiveInMinutes: 1440 maxSize: 0 @@ -718,11 +718,11 @@ queue: security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" other: topic-properties: - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" consumer-stats: enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java index 9c135f549c..bcb1a1abd4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.util.ArrayList; @@ -76,11 +77,11 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testSaveWidgetType() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); Assert.assertNotNull(savedWidgetType); Assert.assertNotNull(savedWidgetType.getId()); @@ -95,42 +96,42 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes doPost("/api/widgetType", savedWidgetType, WidgetType.class); - WidgetType foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetType.class); + WidgetTypeDetails foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class); Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName()); } @Test public void testUpdateWidgetTypeFromDifferentTenant() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); loginDifferentTenant(); - doPost("/api/widgetType", savedWidgetType, WidgetType.class, status().isForbidden()); + doPost("/api/widgetType", savedWidgetType, WidgetTypeDetails.class, status().isForbidden()); deleteDifferentTenant(); } @Test public void testFindWidgetTypeById() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); - WidgetType foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); + WidgetTypeDetails foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class); Assert.assertNotNull(foundWidgetType); Assert.assertEquals(savedWidgetType, foundWidgetType); } @Test public void testDeleteWidgetType() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); doDelete("/api/widgetType/"+savedWidgetType.getId().getId().toString()) .andExpect(status().isOk()); @@ -141,7 +142,7 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testSaveWidgetTypeWithEmptyName() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); doPost("/api/widgetType", widgetType) @@ -151,7 +152,7 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testSaveWidgetTypeWithEmptyBundleAlias() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); doPost("/api/widgetType", widgetType) @@ -161,7 +162,7 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testSaveWidgetTypeWithEmptyDescriptor() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{}", JsonNode.class)); @@ -172,7 +173,7 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testSaveWidgetTypeWithInvalidBundleAlias() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias("some_alias"); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); @@ -183,11 +184,11 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testUpdateWidgetTypeBundleAlias() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); savedWidgetType.setBundleAlias("some_alias"); doPost("/api/widgetType", savedWidgetType) .andExpect(status().isBadRequest()) @@ -197,11 +198,11 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testUpdateWidgetTypeAlias() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); savedWidgetType.setAlias("some_alias"); doPost("/api/widgetType", savedWidgetType) .andExpect(status().isBadRequest()) @@ -213,15 +214,15 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes public void testGetBundleWidgetTypes() throws Exception { List widgetTypes = new ArrayList<>(); for (int i=0;i<89;i++) { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type " + i); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - widgetTypes.add(doPost("/api/widgetType", widgetType, WidgetType.class)); + widgetTypes.add(new WidgetType(doPost("/api/widgetType", widgetType, WidgetTypeDetails.class))); } List loadedWidgetTypes = doGetTyped("/api/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", - new TypeReference>(){}, false, savedWidgetsBundle.getAlias()); + new TypeReference<>(){}, false, savedWidgetsBundle.getAlias()); Collections.sort(widgetTypes, idComparator); Collections.sort(loadedWidgetTypes, idComparator); @@ -231,15 +232,15 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes @Test public void testGetWidgetType() throws Exception { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); WidgetType foundWidgetType = doGet("/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", WidgetType.class, false, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias()); Assert.assertNotNull(foundWidgetType); - Assert.assertEquals(savedWidgetType, foundWidgetType); + Assert.assertEquals(new WidgetType(savedWidgetType), foundWidgetType); } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java deleted file mode 100644 index c091bc3eb0..0000000000 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.mqtt.rpc.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoNoSqlTest -public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlIntegrationTest.java deleted file mode 100644 index f29507dcc5..0000000000 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.mqtt.telemetry.attributes.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; - -@DaoNoSqlTest -public class MqttAttributesNoSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java deleted file mode 100644 index 9031ccdb69..0000000000 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.mqtt.telemetry.attributes.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; - -@DaoNoSqlTest -public class MqttAttributesNoSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java new file mode 100644 index 0000000000..89ef53b19d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java @@ -0,0 +1,191 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.junit.Assert; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractTransportIntegrationTest extends AbstractControllerTest { + + protected static final String MQTT_URL = "tcp://localhost:1883"; + protected static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; + + protected static final AtomicInteger atomicInteger = new AtomicInteger(2); + + protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostTelemetry {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + + protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + + protected Tenant savedTenant; + protected User tenantAdmin; + + protected Device savedDevice; + protected String accessToken; + + protected DeviceProfile deviceProfile; + + protected void processAfterTest() throws Exception { + loginSysAdmin(); + if (savedTenant != null) { + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); + } + } + + protected List getKvProtos(List expectedKeys) { + List keyValueProtos = new ArrayList<>(); + TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V); + TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V); + TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V); + TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V); + TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V); + keyValueProtos.add(strKeyValueProto); + keyValueProtos.add(boolKeyValueProto); + keyValueProtos.add(dblKeyValueProto); + keyValueProtos.add(longKeyValueProto); + keyValueProtos.add(jsonKeyValueProto); + return keyValueProtos; + } + + protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) { + TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder(); + keyValueProtoBuilder.setKey(key); + keyValueProtoBuilder.setType(type); + switch (type) { + case BOOLEAN_V: + keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue)); + break; + case LONG_V: + keyValueProtoBuilder.setLongV(Long.parseLong(strValue)); + break; + case DOUBLE_V: + keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue)); + break; + case STRING_V: + keyValueProtoBuilder.setStringV(strValue); + break; + case JSON_V: + keyValueProtoBuilder.setJsonV(strValue); + break; + } + return keyValueProtoBuilder.build(); + } + + protected T doExecuteWithRetriesAndInterval(SupplierWithThrowable supplier, int retries, int intervalMs) throws Exception { + int count = 0; + T result = null; + Throwable lastException = null; + while (count < retries) { + try { + result = supplier.get(); + if (result != null) { + return result; + } + } catch (Throwable e) { + lastException = e; + } + count++; + if (count < retries) { + Thread.sleep(intervalMs); + } + } + if (lastException != null) { + throw new RuntimeException(lastException); + } else { + return result; + } + } + + @FunctionalInterface + public interface SupplierWithThrowable { + T get() throws Throwable; + } +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java similarity index 92% rename from application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java rename to application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java index 16ebfb8c7c..539a1da055 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt; +package org.thingsboard.server.transport; import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; import org.junit.BeforeClass; @@ -28,8 +28,8 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.mqtt.*.nosql.*Test"}) -public class MqttNoSqlTestSuite { + "org.thingsboard.server.transport.*.telemetry.timeseries.nosql.*Test"}) +public class TransportNoSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java similarity index 70% rename from application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java rename to application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 0dd341f3a6..d16bbc3885 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt; +package org.thingsboard.server.transport; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -26,15 +26,15 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.mqtt.rpc.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", - "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", - "org.thingsboard.server.mqtt.attributes.request.sql.*Test", - "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test" + "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", + "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", + "org.thingsboard.server.transport.*.attributes.request.sql.*Test", + "org.thingsboard.server.transport.*.claim.sql.*Test", + "org.thingsboard.server.transport.*.provision.sql.*Test" }) -public class MqttSqlTestSuite { +public class TransportSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java new file mode 100644 index 0000000000..ff64d70423 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java @@ -0,0 +1,175 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.junit.Assert; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.AbstractTransportIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@Slf4j +public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { + + protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { + this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, DeviceProfileProvisionType.DISABLED, null, null); + } + + protected void processBeforeTest(String deviceName, + CoapDeviceType coapDeviceType, + TransportPayloadType payloadType, + String telemetryProtoSchema, + String attributesProtoSchema, + DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret + ) throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Device device = new Device(); + device.setName(deviceName); + device.setType("default"); + + if (coapDeviceType != null) { + DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, attributesProtoSchema, provisionType, provisionKey, provisionSecret, telemetryProtoSchema); + deviceProfile = doPost("/api/deviceProfile", coapDeviceProfile, DeviceProfile.class); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + } + + savedDevice = doPost("/api/device", device, Device.class); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + + assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + accessToken = deviceCredentials.getCredentialsId(); + assertNotNull(accessToken); + + } + + protected DeviceProfile createCoapDeviceProfile(TransportPayloadType transportPayloadType, CoapDeviceType coapDeviceType, + String attributesProtoSchema, DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret, String telemetryProtoSchema) { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setName(transportPayloadType.name()); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setProvisionType(provisionType); + deviceProfile.setProvisionDeviceKey(provisionKey); + deviceProfile.setDescription(transportPayloadType.name() + " Test"); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + deviceProfile.setTransportType(DeviceTransportType.COAP); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = new CoapDeviceProfileTransportConfiguration(); + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration; + if (CoapDeviceType.DEFAULT.equals(coapDeviceType)) { + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + if (TransportPayloadType.PROTOBUF.equals(transportPayloadType)) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + if (StringUtils.isEmpty(telemetryProtoSchema)) { + telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; + } + if (StringUtils.isEmpty(attributesProtoSchema)) { + attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; + } + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; + } else { + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); + } + defaultCoapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + coapDeviceTypeConfiguration = defaultCoapDeviceTypeConfiguration; + } else { + coapDeviceTypeConfiguration = new EfentoCoapDeviceTypeConfiguration(); + } + coapDeviceProfileTransportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); + deviceProfileData.setTransportConfiguration(coapDeviceProfileTransportConfiguration); + DeviceProfileProvisionConfiguration provisionConfiguration; + switch (provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case DISABLED: + default: + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + break; + } + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + deviceProfileData.setConfiguration(configuration); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setDefault(false); + deviceProfile.setDefaultRuleChainId(null); + return deviceProfile; + } + + protected CoapClient getCoapClient(FeatureType featureType) { + return new CoapClient(getFeatureTokenUrl(accessToken, featureType)); + } + + protected CoapClient getCoapClient(String featureTokenUrl) { + return new CoapClient(featureTokenUrl); + } + + protected String getFeatureTokenUrl(String token, FeatureType featureType) { + return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java new file mode 100644 index 0000000000..53d345ec3d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { + + protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," + + "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; + + protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { + super.processBeforeTest(deviceName, coapDeviceType, payloadType); + } + + protected void processAfterTest() throws Exception { + super.processAfterTest(); + } + + protected List getTsKvProtoList() { + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V); + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V); + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V); + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V); + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); + List tsKvProtoList = new ArrayList<>(); + tsKvProtoList.add(tsKvProtoAttribute1); + tsKvProtoList.add(tsKvProtoAttribute2); + tsKvProtoList.add(tsKvProtoAttribute3); + tsKvProtoList.add(tsKvProtoAttribute4); + tsKvProtoList.add(tsKvProtoAttribute5); + return tsKvProtoList; + } + + protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) { + TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder(); + TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType); + tsKvProtoBuilder.setKv(keyValueProto); + return tsKvProtoBuilder.build(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java new file mode 100644 index 0000000000..999344b5f3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.protobuf.InvalidProtocolBufferException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapAttributesRequestIntegrationTest extends AbstractCoapAttributesIntegrationTest { + + protected static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Request attribute values from the server", null, null); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testRequestAttributesValuesFromTheServer() throws Exception { + processTestRequestAttributesValuesFromTheServer(); + } + + protected void processTestRequestAttributesValuesFromTheServer() throws Exception { + postAttributes(); + + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List savedAttributeKeys = null; + while (start <= end) { + savedAttributeKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); + if (savedAttributeKeys.size() == 5) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(savedAttributeKeys); + + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; + String featureTokenUrl = getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keys + "&sharedKeys=" + keys; + CoapClient client = getCoapClient(featureTokenUrl); + + CoapResponse getAttributesResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); + validateResponse(getAttributesResponse); + } + + protected void postAttributes() throws Exception { + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); + CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(POST_ATTRIBUTES_PAYLOAD.getBytes(), MediaTypeRegistry.APPLICATION_JSON); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + } + + protected void validateResponse(CoapResponse getAttributesResponse) throws InvalidProtocolBufferException { + assertEquals(CoAP.ResponseCode.CONTENT, getAttributesResponse.getCode()); + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(getAttributesResponse.getPayload(), StandardCharsets.UTF_8))); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java new file mode 100644 index 0000000000..eb479fa2ad --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapAttributesRequestJsonIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Request attribute values from the server json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testRequestAttributesValuesFromTheServer() throws Exception { + super.testRequestAttributesValuesFromTheServer(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java new file mode 100644 index 0000000000..4fbe103430 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java @@ -0,0 +1,153 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.junit.After; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { + + public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string attribute1 = 1;\n" + + " bool attribute2 = 2;\n" + + " double attribute3 = 3;\n" + + " int32 attribute4 = 4;\n" + + " JsonObject attribute5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testRequestAttributesValuesFromTheServer() throws Exception { + super.processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT, + TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); + processTestRequestAttributesValuesFromTheServer(); + } + + protected void postAttributes() throws Exception { + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) + .build(); + byte[] payload = postAttributesMsg.toByteArray(); + CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); + CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(payload, MediaTypeRegistry.APPLICATION_JSON); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + } + + protected void validateResponse(CoapResponse getAttributesResponse) throws InvalidProtocolBufferException { + TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg(); + TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(getAttributesResponse.getPayload()); + assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId()); + List expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); + assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos)); + } + + private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() { + TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder(); + List tsKvProtoList = getTsKvProtoList(); + result.addAllClientAttributeList(tsKvProtoList); + result.addAllSharedAttributeList(tsKvProtoList); + result.setRequestId(0); + return result.build(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java new file mode 100644 index 0000000000..5e9b788ec0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request.sql; + +import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestJsonIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesRequestJsonSqlIntegrationTest extends AbstractCoapAttributesRequestJsonIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java new file mode 100644 index 0000000000..e08a09bcb2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request.sql; + +import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestProtoIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesRequestProtoSqlIntegrationTest extends AbstractCoapAttributesRequestProtoIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java new file mode 100644 index 0000000000..bb01f20b11 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.request.sql; + +import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesRequestSqlIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java new file mode 100644 index 0000000000..faa6aae5c4 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java @@ -0,0 +1,141 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates; + +import com.google.protobuf.InvalidProtocolBufferException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributesIntegrationTest { + + private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Subscribe to attribute updates", null, null); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + protected void processTestSubscribeToAttributesUpdates() throws Exception { + + CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); + + CountDownLatch latch = new CountDownLatch(1); + TestCoapCallback testCoapCallback = new TestCoapCallback(latch); + + Request request = Request.newGet().setObserve(); + request.setType(CoAP.Type.CON); + CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); + + Thread.sleep(1000); + + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + latch.await(3, TimeUnit.SECONDS); + + validateUpdateAttributesResponse(testCoapCallback); + + latch = new CountDownLatch(1); + + doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); + latch.await(3, TimeUnit.SECONDS); + + validateDeleteAttributesResponse(testCoapCallback); + + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + } + + protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + assertNotNull(callback.getObserve()); + assertEquals(0, callback.getObserve().intValue()); + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); + assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response)); + } + + protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + assertNotNull(callback.getObserve()); + assertEquals(1, callback.getObserve().intValue()); + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); + assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response)); + } + + protected static class TestCoapCallback implements CoapHandler { + + private final CountDownLatch latch; + + private Integer observe; + private byte[] payloadBytes; + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public Integer getObserve() { + return observe; + } + + private TestCoapCallback(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onLoad(CoapResponse response) { + assertNotNull(response.getPayload()); + assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT); + observe = response.getOptions().getObserve(); + payloadBytes = response.getPayload(); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java new file mode 100644 index 0000000000..adfef9fbef --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Subscribe to attribute updates", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + super.testSubscribeToAttributesUpdatesFromTheServer(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java new file mode 100644 index 0000000000..378354123e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates; + +import com.google.protobuf.InvalidProtocolBufferException; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@Slf4j +public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Subscribe to attribute updates", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); + List tsKvProtoList = getTsKvProtoList(); + attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); + + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); + + List actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); + + } + + protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); + attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); + + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); + + assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size()); + assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0)); + + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java new file mode 100644 index 0000000000..f0fce3a0c5 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates.sql; + +import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesUpdatesSqlIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java new file mode 100644 index 0000000000..9ca721503d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates.sql; + +import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesJsonIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesUpdatesSqlJsonIntegrationTest extends AbstractCoapAttributesUpdatesJsonIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java new file mode 100644 index 0000000000..cf8e453973 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.attributes.updates.sql; + +import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesProtoIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapAttributesUpdatesSqlProtoIntegrationTest extends AbstractCoapAttributesUpdatesProtoIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java new file mode 100644 index 0000000000..d3334ea150 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.ClaimRequest; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.device.claim.ClaimResponse; +import org.thingsboard.server.dao.device.claim.ClaimResult; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapClaimDeviceTest extends AbstractCoapIntegrationTest { + + protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!"; + + protected User customerAdmin; + protected Customer savedCustomer; + + @Before + public void beforeTest() throws Exception { + super.processBeforeTest("Test Claim device", null, null); + createCustomerAndUser(); + } + + protected void createCustomerAndUser() throws Exception { + Customer customer = new Customer(); + customer.setTenantId(savedTenant.getId()); + customer.setTitle("Test Claiming Customer"); + savedCustomer = doPost("/api/customer", customer, Customer.class); + assertNotNull(savedCustomer); + assertEquals(savedTenant.getId(), savedCustomer.getTenantId()); + + User user = new User(); + user.setAuthority(Authority.CUSTOMER_USER); + user.setTenantId(savedTenant.getId()); + user.setCustomerId(savedCustomer.getId()); + user.setEmail("customer@thingsboard.org"); + + customerAdmin = createUser(user, CUSTOMER_USER_PASSWORD); + assertNotNull(customerAdmin); + assertEquals(customerAdmin.getCustomerId(), savedCustomer.getId()); + } + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testClaimingDevice() throws Exception { + processTestClaimingDevice(false); + } + + @Test + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception { + processTestClaimingDevice(true); + } + + protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { + log.warn("[testClaimingDevice] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType()); + CoapClient client = getCoapClient(FeatureType.CLAIM); + byte[] payloadBytes; + byte[] failurePayloadBytes; + if (emptyPayload) { + payloadBytes = "{}".getBytes(); + failurePayloadBytes = "{\"durationMs\":1}".getBytes(); + } else { + payloadBytes = "{\"secretKey\":\"value\", \"durationMs\":60000}".getBytes(); + failurePayloadBytes = "{\"secretKey\":\"value\", \"durationMs\":1}".getBytes(); + } + validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes); + } + + protected void validateClaimResponse(boolean emptyPayload, CoapClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception { + postClaimRequest(client, failurePayloadBytes); + + loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD); + ClaimRequest claimRequest; + if (!emptyPayload) { + claimRequest = new ClaimRequest("value"); + } else { + claimRequest = new ClaimRequest(null); + } + + ClaimResponse claimResponse = doExecuteWithRetriesAndInterval( + () -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest()), + 20, + 100 + ); + + assertEquals(claimResponse, ClaimResponse.FAILURE); + + postClaimRequest(client, payloadBytes); + + ClaimResult claimResult = doExecuteWithRetriesAndInterval( + () -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk()), + 20, + 100 + ); + assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS); + Device claimedDevice = claimResult.getDevice(); + assertNotNull(claimedDevice); + assertNotNull(claimedDevice.getCustomerId()); + assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId()); + + claimResponse = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest()); + assertEquals(claimResponse, ClaimResponse.CLAIMED); + } + + private void postClaimRequest(CoapClient client, byte[] payload) throws IOException, ConnectorException { + CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java new file mode 100644 index 0000000000..4455feffb0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapClaimJsonDeviceTest extends AbstractCoapClaimDeviceTest { + + @Before + public void beforeTest() throws Exception { + super.processBeforeTest("Test Claim device Json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + createCustomerAndUser(); + } + + @After + public void afterTest() throws Exception { + super.afterTest(); + } + + @Test + public void testClaimingDevice() throws Exception { + super.testClaimingDevice(); + } + + @Test + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception { + super.testClaimingDeviceWithoutSecretAndDuration(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java new file mode 100644 index 0000000000..a9691e6693 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.gen.transport.TransportApiProtos; + +@Slf4j +public abstract class AbstractCoapClaimProtoDeviceTest extends AbstractCoapClaimDeviceTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Claim device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + createCustomerAndUser(); + } + + @After + public void afterTest() throws Exception { super.afterTest(); } + + @Test + public void testClaimingDevice() throws Exception { + processTestClaimingDevice(false); + } + + @Test + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception { + processTestClaimingDevice(true); + } + + @Override + protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { + CoapClient client = getCoapClient(FeatureType.CLAIM); + byte[] payloadBytes; + if (emptyPayload) { + TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(0, emptyPayload); + payloadBytes = claimDevice.toByteArray(); + } else { + TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(60000, emptyPayload); + payloadBytes = claimDevice.toByteArray(); + } + TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(1, emptyPayload); + byte[] failurePayloadBytes = claimDevice.toByteArray(); + validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes); + } + + private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) { + TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder(); + if (!emptyPayload) { + claimDeviceBuilder.setSecretKey("value"); + } + if (duration > 0) { + claimDeviceBuilder.setSecretKey("value"); + claimDeviceBuilder.setDurationMs(duration); + } else { + claimDeviceBuilder.setDurationMs(0); + } + return claimDeviceBuilder.build(); + } + + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java new file mode 100644 index 0000000000..1acec62e8d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim.sql; + +import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimJsonDeviceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapClaimDeviceJsonSqlTest extends AbstractCoapClaimJsonDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java new file mode 100644 index 0000000000..2b04651f47 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim.sql; + +import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimProtoDeviceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapClaimDeviceProtoSqlTest extends AbstractCoapClaimProtoDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java new file mode 100644 index 0000000000..9548009ec7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.claim.sql; + +import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimDeviceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapClaimDeviceSqlTest extends AbstractCoapClaimDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java new file mode 100644 index 0000000000..b4a55ae271 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java @@ -0,0 +1,195 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.provision; + +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +@Slf4j +public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + private void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + byte[] result = createCoapClientAndPublish().getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); + } + + + private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createCoapClientAndPublish().getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + } + + + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; + byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + } + + + private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; + byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + } + + private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createCoapClientAndPublish().getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + } + + private void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + byte[] result = createCoapClientAndPublish().getPayload(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); + } + + private CoapResponse createCoapClientAndPublish() throws Exception { + return createCoapClientAndPublish(""); + } + + private CoapResponse createCoapClientAndPublish(String deviceCredentials) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); + CoapClient client = getCoapClient(FeatureType.PROVISION); + return postProvision(client, provisionRequestMsg.getBytes()); + } + + + private CoapResponse postProvision(CoapClient client, byte[] payload) throws IOException, ConnectorException { + return client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + } + + private String createTestProvisionMessage(String deviceCredentials) { + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java new file mode 100644 index 0000000000..9f1d8f1f18 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java @@ -0,0 +1,205 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.provision; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; + +import java.io.IOException; + +@Slf4j +public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + private void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + Assert.assertNotNull(result); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); + } + + private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + } + + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayload()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + } + + private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayload()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + } + + private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + } + + private void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); + } + + private CoapResponse createCoapClientAndPublish() throws Exception { + byte[] provisionRequestMsg = createTestProvisionMessage(); + return createCoapClientAndPublish(provisionRequestMsg); + } + + private CoapResponse createCoapClientAndPublish(byte[] provisionRequestMsg) throws Exception { + CoapClient client = getCoapClient(FeatureType.PROVISION); + return postProvision(client, provisionRequestMsg); + } + + private CoapResponse postProvision(CoapClient client, byte[] payload) throws IOException, ConnectorException { + return client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + } + + private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return ProvisionDeviceRequestMsg.newBuilder() + .setDeviceName("Test Provision device") + .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) + .setCredentialsDataProto(credentialsData != null ? credentialsData: CredentialsDataProto.newBuilder().build()) + .setProvisionDeviceCredentialsMsg( + ProvisionDeviceCredentialsMsg.newBuilder() + .setProvisionDeviceKey("testProvisionKey") + .setProvisionDeviceSecret("testProvisionSecret") + ).build() + .toByteArray(); + } + + + private byte[] createTestProvisionMessage() throws Exception { + return createTestsProvisionMessage(null, null); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java new file mode 100644 index 0000000000..598041f0ee --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.provision.sql; + +import org.thingsboard.server.transport.coap.provision.AbstractCoapProvisionJsonDeviceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapProvisionDeviceJsonSqlTest extends AbstractCoapProvisionJsonDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java new file mode 100644 index 0000000000..cde83c41e7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.provision.sql; + +import org.thingsboard.server.transport.coap.provision.AbstractCoapProvisionProtoDeviceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapProvisionDeviceProtoSqlTest extends AbstractCoapProvisionProtoDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java new file mode 100644 index 0000000000..a4210aa31a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.service.security.AccessValidator; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("RPC test device", null, null); + } + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testServerCoapOneWayRpcDeviceOffline() throws Exception { + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; + String deviceId = savedDevice.getId().getId().toString(); + + doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), + asyncContextTimeoutToUseRpcPlugin); + } + + @Test + public void testServerCoapOneWayRpcDeviceDoesNotExist() throws Exception { + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}"; + String nonExistentDeviceId = Uuids.timeBased().toString(); + + String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, + status().isNotFound()); + Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); + } + + @Test + public void testServerCoapTwoWayRpcDeviceOffline() throws Exception { + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}"; + String deviceId = savedDevice.getId().getId().toString(); + + doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409), + asyncContextTimeoutToUseRpcPlugin); + } + + @Test + public void testServerCoapTwoWayRpcDeviceDoesNotExist() throws Exception { + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}"; + String nonExistentDeviceId = Uuids.timeBased().toString(); + + String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, + status().isNotFound()); + Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); + } + + @Test + public void testServerCoapOneWayRpc() throws Exception { + processOneWayRpcTest(); + } + + @Test + public void testServerCoapTwoWayRpc() throws Exception { + processTwoWayRpcTest(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java new file mode 100644 index 0000000000..a36660267e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -0,0 +1,165 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Request; +import org.junit.Assert; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractCoapIntegrationTest { + + protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; + + protected Long asyncContextTimeoutToUseRpcPlugin; + + protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { + super.processBeforeTest(deviceName, coapDeviceType, payloadType); + asyncContextTimeoutToUseRpcPlugin = 10000L; + } + + protected void processOneWayRpcTest() throws Exception { + CoapClient client = getCoapClient(FeatureType.RPC); + client.useCONs(); + + CountDownLatch latch = new CountDownLatch(1); + TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, true); + + Request request = Request.newGet().setObserve(); + CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); + + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; + String deviceId = savedDevice.getId().getId().toString(); + String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); + Assert.assertTrue(StringUtils.isEmpty(result)); + latch.await(3, TimeUnit.SECONDS); + assertEquals(0, testCoapCallback.getObserve().intValue()); + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + } + + protected void processTwoWayRpcTest() throws Exception { + CoapClient client = getCoapClient(FeatureType.RPC); + client.useCONs(); + + CountDownLatch latch = new CountDownLatch(1); + TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, false); + + Request request = Request.newGet().setObserve(); + request.setType(CoAP.Type.CON); + CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); + + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; + String deviceId = savedDevice.getId().getId().toString(); + + String expected = "{\"value1\":\"A\",\"value2\":\"B\"}"; + + String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + latch.await(3, TimeUnit.SECONDS); + + assertEquals(expected, result); + assertEquals(0, testCoapCallback.getObserve().intValue()); + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + +// // TODO: 3/11/21 Fix test to validate next RPC +// latch = new CountDownLatch(1); +// +// result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); +// latch.await(3, TimeUnit.SECONDS); +// +// assertEquals(expected, result); +// assertEquals(1, testCoapCallback.getObserve().intValue()); + } + + protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { + client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe)); + client.post(new CoapHandler() { + @Override + public void onLoad(CoapResponse response) { + log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + }, DEVICE_RESPONSE, MediaTypeRegistry.APPLICATION_JSON); + } + + protected String getRpcResponseFeatureTokenUrl(String token, int requestId) { + return COAP_BASE_URL + token + "/" + FeatureType.RPC.name().toLowerCase() + "/" + requestId; + } + + private class TestCoapCallback implements CoapHandler { + + private final CoapClient client; + private final CountDownLatch latch; + private final boolean isOneWayRpc; + + public Integer getObserve() { + return observe; + } + + private Integer observe; + + private TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) { + this.client = client; + this.latch = latch; + this.isOneWayRpc = isOneWayRpc; + } + + @Override + public void onLoad(CoapResponse response) { + log.warn("coap response: {}, {}", response, response.getCode()); + assertNotNull(response.getPayload()); + assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT); + observe = response.getOptions().getObserve(); + if (!isOneWayRpc) { + processOnLoadResponse(response, client, observe, latch); + } else { + latch.countDown(); + } + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java new file mode 100644 index 0000000000..5387d3c7bf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + } + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testServerMqttOneWayRpc() throws Exception { + processOneWayRpcTest(); + } + + @Test + public void testServerMqttTwoWayRpc() throws Exception { + processTwoWayRpcTest(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java new file mode 100644 index 0000000000..edbdf69848 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.concurrent.CountDownLatch; + +@Slf4j +public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + } + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testServerMqttOneWayRpc() throws Exception { + processOneWayRpcTest(); + } + + @Test + public void testServerMqttTwoWayRpc() throws Exception { + processTwoWayRpcTest(); + } + + @Override + protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { + client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe)); + TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setPayload(DEVICE_RESPONSE) + .setRequestId(observe) + .build(); + client.post(new CoapHandler() { + @Override + public void onLoad(CoapResponse response) { + log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + }, toDeviceRpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java new file mode 100644 index 0000000000..ec417fa45f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc.sql; + +import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcJsonIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapServerSideRpcJsonSqlIntegrationTest extends AbstractCoapServerSideRpcJsonIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java new file mode 100644 index 0000000000..9ed0a13019 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc.sql; + +import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcProtoIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +@DaoSqlTest +public class CoapServerSideRpcProtoSqlIntegrationTest extends AbstractCoapServerSideRpcProtoIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java new file mode 100644 index 0000000000..b505de2f39 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.rpc.sql; + +import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcDefaultIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class CoapServerSideRpcSqlIntegrationTest extends AbstractCoapServerSideRpcDefaultIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java new file mode 100644 index 0000000000..b01c011b41 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java @@ -0,0 +1,145 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@Slf4j +public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { + + private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Post Attributes device", null, null); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testPushAttributes() throws Exception { + List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); + processAttributesTest(expectedKeys, PAYLOAD_VALUES_STR.getBytes()); + } + + protected void processAttributesTest(List expectedKeys, byte[] payload) throws Exception { + log.warn("[testPushAttributes] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType()); + CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); + + postAttributes(client, payload); + + DeviceId deviceId = savedDevice.getId(); + + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List actualKeys = null; + while (start <= end) { + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); + if (actualKeys.size() == expectedKeys.size()) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(actualKeys); + + Set actualKeySet = new HashSet<>(actualKeys); + + Set expectedKeySet = new HashSet<>(expectedKeys); + + assertEquals(expectedKeySet, actualKeySet); + + String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet); + List> values = doGetAsyncTyped(getAttributesValuesUrl, new TypeReference<>() {}); + assertAttributesValues(values, expectedKeySet); + String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet); + doDelete(deleteAttributesUrl); + } + + private void postAttributes(CoapClient client, byte[] payload) throws IOException, ConnectorException { + if (payload == null) { + payload = PAYLOAD_VALUES_STR.getBytes(); + } + CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + } + + @SuppressWarnings("unchecked") + protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) throws JsonProcessingException { + for (Map map : deviceValues) { + String key = (String) map.get("key"); + Object value = map.get("value"); + assertTrue(expectedKeySet.contains(key)); + switch (key) { + case "key1": + assertEquals("value1", value); + break; + case "key2": + assertEquals(true, value); + break; + case "key3": + assertEquals(3.0, value); + break; + case "key4": + assertEquals(4, value); + break; + case "key5": + assertNotNull(value); + assertEquals(3, ((LinkedHashMap) value).size()); + assertEquals(42, ((LinkedHashMap) value).get("someNumber")); + assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray")); + LinkedHashMap someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject"); + assertEquals("value", someNestedObject.get("key")); + break; + } + } + } + + private String getAttributesValuesUrl(DeviceId deviceId, Set actualKeySet) { + return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java new file mode 100644 index 0000000000..a07d02bcd7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapAttributesJsonIntegrationTest extends AbstractCoapAttributesIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Post Attributes device Json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testPushAttributes() throws Exception { + super.testPushAttributes(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java new file mode 100644 index 0000000000..5f339ccffe --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@Slf4j +public abstract class AbstractCoapAttributesProtoIntegrationTest extends AbstractCoapAttributesIntegrationTest { + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testPushAttributes() throws Exception { + super.processBeforeTest("Test Post Attributes device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) + .build(); + processAttributesTest(expectedKeys, postAttributesMsg.toByteArray()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java new file mode 100644 index 0000000000..4a2b655214 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes.sql; + +import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapAttributesSqlIntegrationTest extends AbstractCoapAttributesIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java new file mode 100644 index 0000000000..eb4981414e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes.sql; + +import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesJsonIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapAttributesSqlJsonIntegrationTest extends AbstractCoapAttributesJsonIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java new file mode 100644 index 0000000000..aabb98cfba --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.attributes.sql; + +import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesProtoIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapAttributesSqlProtoIntegrationTest extends AbstractCoapAttributesProtoIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java new file mode 100644 index 0000000000..2c16725f9b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java @@ -0,0 +1,178 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@Slf4j +public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoapIntegrationTest { + + private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Post Telemetry device", null, null); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testPushTelemetry() throws Exception { + processTestPostTelemetry(null, false); + } + + @Test + public void testPushTelemetryWithTs() throws Exception { + String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}"; + processTestPostTelemetry(payloadStr.getBytes(), true); + } + + protected void processTestPostTelemetry(byte[] payloadBytes, boolean withTs) throws Exception { + log.warn("[testPushTelemetry] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType()); + List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); + CoapClient coapClient = getCoapClient(FeatureType.TELEMETRY); + postTelemetry(coapClient, payloadBytes); + + String deviceId = savedDevice.getId().getId().toString(); + + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List actualKeys = null; + while (start <= end) { + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {}); + if (actualKeys.size() == expectedKeys.size()) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(actualKeys); + + Set actualKeySet = new HashSet<>(actualKeys); + Set expectedKeySet = new HashSet<>(expectedKeys); + + assertEquals(expectedKeySet, actualKeySet); + + String getTelemetryValuesUrl; + if (withTs) { + getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=15000&keys=" + String.join(",", actualKeySet); + } else { + getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); + } + start = System.currentTimeMillis(); + end = System.currentTimeMillis() + 5000; + Map>> values = null; + while (start <= end) { + values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {}); + boolean valid = values.size() == expectedKeys.size(); + if (valid) { + for (String key : expectedKeys) { + List> tsValues = values.get(key); + if (tsValues != null && tsValues.size() > 0) { + Object ts = tsValues.get(0).get("ts"); + if (ts == null) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + } + if (valid) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(values); + + if (withTs) { + assertTs(values, expectedKeys, 10000, 0); + } + assertValues(values, 0); + } + + private void postTelemetry(CoapClient client, byte[] payload) throws IOException, ConnectorException { + if (payload == null) { + payload = PAYLOAD_VALUES_STR.getBytes(); + } + CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + } + + private void assertTs(Map>> deviceValues, List expectedKeys, int ts, int arrayIndex) { + assertEquals(ts, deviceValues.get(expectedKeys.get(0)).get(arrayIndex).get("ts")); + assertEquals(ts, deviceValues.get(expectedKeys.get(1)).get(arrayIndex).get("ts")); + assertEquals(ts, deviceValues.get(expectedKeys.get(2)).get(arrayIndex).get("ts")); + assertEquals(ts, deviceValues.get(expectedKeys.get(3)).get(arrayIndex).get("ts")); + assertEquals(ts, deviceValues.get(expectedKeys.get(4)).get(arrayIndex).get("ts")); + } + + private void assertValues(Map>> deviceValues, int arrayIndex) { + for (Map.Entry>> entry : deviceValues.entrySet()) { + String key = entry.getKey(); + List> tsKv = entry.getValue(); + String value = (String) tsKv.get(arrayIndex).get("value"); + switch (key) { + case "key1": + assertEquals("value1", value); + break; + case "key2": + assertEquals("true", value); + break; + case "key3": + assertEquals("3.0", value); + break; + case "key4": + assertEquals("4", value); + break; + case "key5": + assertEquals("{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", value); + break; + } + } + } + + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java new file mode 100644 index 0000000000..ab05773cab --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +public abstract class AbstractCoapTimeseriesJsonIntegrationTest extends AbstractCoapTimeseriesIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest("Test Post Telemetry device json payload", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + } + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + + @Test + public void testPushTelemetry() throws Exception { + super.testPushTelemetry(); + } + + @Test + public void testPushTelemetryWithTs() throws Exception { + super.testPushTelemetryWithTs(); + } + + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java new file mode 100644 index 0000000000..2ae686b211 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java @@ -0,0 +1,170 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@Slf4j +public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends AbstractCoapTimeseriesIntegrationTest { + + @After + public void afterTest() throws Exception { + processAfterTest(); + } + + @Test + public void testPushTelemetry() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) + .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) + .build(); + processTestPostTelemetry(postTelemetryMsg.toByteArray(), false); + } + + @Test + public void testPushTelemetryWithTs() throws Exception { + String schemaStr = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostTelemetry {\n" + + " int64 ts = 1;\n" + + " Values values = 2;\n" + + " \n" + + " message Values {\n" + + " string key1 = 3;\n" + + " bool key2 = 4;\n" + + " double key3 = 5;\n" + + " int32 key4 = 6;\n" + + " JsonObject key5 = 7;\n" + + " }\n" + + " \n" + + " message JsonObject {\n" + + " int32 someNumber = 8;\n" + + " repeated int32 someArray = 9;\n" + + " NestedJsonObject someNestedObject = 10;\n" + + " message NestedJsonObject {\n" + + " string key = 11;\n" + + " }\n" + + " }\n" + + "}"; + super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); + Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); + assertNotNull(valuesDescriptor); + + DynamicMessage valuesMsg = valuesBuilder + .setField(valuesDescriptor.findFieldByName("key1"), "value1") + .setField(valuesDescriptor.findFieldByName("key2"), true) + .setField(valuesDescriptor.findFieldByName("key3"), 3.0) + .setField(valuesDescriptor.findFieldByName("key4"), 4) + .setField(valuesDescriptor.findFieldByName("key5"), jsonObject) + .build(); + + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("ts"), 10000L) + .setField(postTelemetryMsgDescriptor.findFieldByName("values"), valuesMsg) + .build(); + + processTestPostTelemetry(postTelemetryMsg.toByteArray(), true); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlIntegrationTest.java similarity index 71% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlIntegrationTest.java index 2c2a51a614..459e7cae7b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlIntegrationTest.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes.nosql; +package org.thingsboard.server.transport.coap.telemetry.timeseries.nosql; +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesIntegrationTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; @DaoNoSqlTest -public class MqttAttributesNoSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest { +public class CoapTimeseriesNoSqlIntegrationTest extends AbstractCoapTimeseriesIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlJsonIntegrationTest.java similarity index 70% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlJsonIntegrationTest.java index e4faa4c943..ec4988875b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlJsonIntegrationTest.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates.nosql; +package org.thingsboard.server.transport.coap.telemetry.timeseries.nosql; +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesJsonIntegrationTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; - @DaoNoSqlTest -public class MqttAttributesUpdatesNoSqlIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest { +public class CoapTimeseriesNoSqlJsonIntegrationTest extends AbstractCoapTimeseriesJsonIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlProtoIntegrationTest.java similarity index 70% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlProtoIntegrationTest.java index a3f15ecf53..46ce05aa7b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/nosql/CoapTimeseriesNoSqlProtoIntegrationTest.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim.nosql; +package org.thingsboard.server.transport.coap.telemetry.timeseries.nosql; +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesProtoIntegrationTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest; - @DaoNoSqlTest -public class MqttClaimDeviceNoSqlTest extends AbstractMqttClaimDeviceTest { +public class CoapTimeseriesNoSqlProtoIntegrationTest extends AbstractCoapTimeseriesProtoIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlIntegrationTest.java new file mode 100644 index 0000000000..c1f4e4f2c7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries.sql; + +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapTimeseriesSqlIntegrationTest extends AbstractCoapTimeseriesIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlJsonIntegrationTest.java new file mode 100644 index 0000000000..19b13e2293 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlJsonIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries.sql; + +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesJsonIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapTimeseriesSqlJsonIntegrationTest extends AbstractCoapTimeseriesJsonIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlProtoIntegrationTest.java new file mode 100644 index 0000000000..acec631414 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/sql/CoapTimeseriesSqlProtoIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.telemetry.timeseries.sql; + +import org.thingsboard.server.transport.coap.telemetry.timeseries.AbstractCoapTimeseriesProtoIntegrationTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class CoapTimeseriesSqlProtoIntegrationTest extends AbstractCoapTimeseriesProtoIntegrationTest { +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java similarity index 69% rename from application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 9882769868..cd074b3197 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt; +package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; @@ -44,71 +44,17 @@ import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadCo import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.AbstractTransportIntegrationTest; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest { - - protected static final String MQTT_URL = "tcp://localhost:1883"; - - private static final AtomicInteger atomicInteger = new AtomicInteger(2); - - public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + - "\n" + - "package test;\n" + - "\n" + - "message PostTelemetry {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " JsonObject key5 = 5;\n" + - "\n" + - " message JsonObject {\n" + - " int32 someNumber = 6;\n" + - " repeated int32 someArray = 7;\n" + - " NestedJsonObject someNestedObject = 8;\n" + - " message NestedJsonObject {\n" + - " string key = 9;\n" + - " }\n" + - " }\n" + - "}"; - - public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + - "\n" + - "package test;\n" + - "\n" + - "message PostAttributes {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " JsonObject key5 = 5;\n" + - "\n" + - " message JsonObject {\n" + - " int32 someNumber = 6;\n" + - " repeated int32 someArray = 7;\n" + - " NestedJsonObject someNestedObject = 8;\n" + - " message NestedJsonObject {\n" + - " string key = 9;\n" + - " }\n" + - " }\n" + - "}"; - - protected Tenant savedTenant; - protected User tenantAdmin; - - protected Device savedDevice; - protected String accessToken; +public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest { protected Device savedGateway; protected String gatewayAccessToken; @@ -208,45 +154,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest client.publish(topic, message); } - protected List getKvProtos(List expectedKeys) { - List keyValueProtos = new ArrayList<>(); - TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V); - TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V); - TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V); - TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V); - TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V); - keyValueProtos.add(strKeyValueProto); - keyValueProtos.add(boolKeyValueProto); - keyValueProtos.add(dblKeyValueProto); - keyValueProtos.add(longKeyValueProto); - keyValueProtos.add(jsonKeyValueProto); - return keyValueProtos; - } - - protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) { - TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder(); - keyValueProtoBuilder.setKey(key); - keyValueProtoBuilder.setType(type); - switch (type) { - case BOOLEAN_V: - keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue)); - break; - case LONG_V: - keyValueProtoBuilder.setLongV(Long.parseLong(strValue)); - break; - case DOUBLE_V: - keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue)); - break; - case STRING_V: - keyValueProtoBuilder.setStringV(strValue); - break; - case JSON_V: - keyValueProtoBuilder.setJsonV(strValue); - break; - } - return keyValueProtoBuilder.build(); - } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, String telemetryProtoSchema, String attributesProtoSchema, @@ -312,34 +219,4 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest builder.addAllKv(kvProtos); return builder.build(); } - - protected T doExecuteWithRetriesAndInterval(SupplierWithThrowable supplier, int retries, int intervalMs) throws Exception { - int count = 0; - T result = null; - Throwable lastException = null; - while (count < retries) { - try { - result = supplier.get(); - if (result != null) { - return result; - } - } catch (Throwable e) { - lastException = e; - } - count++; - if (count < retries) { - Thread.sleep(intervalMs); - } - } - if (lastException != null) { - throw new RuntimeException(lastException); - } else { - return result; - } - } - - @FunctionalInterface - public interface SupplierWithThrowable { - T get() throws Throwable; - } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java similarity index 97% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 4065a235c2..edbe2fcd28 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes; +package org.thingsboard.server.transport.mqtt.attributes; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; @@ -21,7 +21,7 @@ import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.ArrayList; import java.util.List; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 6ad5db9aff..a75ad9b5ce 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request; +package org.thingsboard.server.transport.mqtt.attributes.request; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; @@ -27,7 +27,7 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java similarity index 96% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java index ade21db516..c805572e3d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request; +package org.thingsboard.server.transport.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; import org.junit.After; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java similarity index 95% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 0547d73f59..ff8b54c79c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request; +package org.thingsboard.server.transport.mqtt.attributes.request; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.google.protobuf.Descriptors; @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.util.ArrayList; import java.util.Arrays; @@ -93,7 +94,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends } protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception { - doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", AbstractMqttAttributesIntegrationTest.POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; @@ -148,7 +149,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes)); } - protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { + protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder(); attributesRequestBuilder.setClientKeys(keys); @@ -170,7 +171,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos)); } - protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { + protected void validateClientResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true); client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray())); @@ -189,7 +190,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); } - protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { + protected void validateSharedResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false); client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray())); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java index 4f177d2c80..b6540832be 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request.sql; +package org.thingsboard.server.transport.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; @DaoSqlTest public class MqttAttributesRequestJsonSqlIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java index e90f8e6ad6..4615b75824 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request.sql; +package org.thingsboard.server.transport.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; @DaoSqlTest public class MqttAttributesRequestProtoSqlIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java index ed8fc563eb..89253462f9 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request.sql; +package org.thingsboard.server.transport.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; @DaoSqlTest public class MqttAttributesRequestSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java similarity index 97% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index 9810c4fe3a..61c5a25303 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates; +package org.thingsboard.server.transport.mqtt.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java similarity index 95% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 81021cd085..54b2abccc3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates; +package org.thingsboard.server.transport.mqtt.attributes.updates; import lombok.extern.slf4j.Slf4j; import org.junit.After; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index f0458be746..6c2565a016 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates; +package org.thingsboard.server.transport.mqtt.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java index 2bceaec71d..0f5dfdda46 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates.sql; +package org.thingsboard.server.transport.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; @DaoSqlTest public class MqttAttributesUpdatesSqlIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java index 3abab9f052..54b5060886 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates.sql; +package org.thingsboard.server.transport.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; @DaoSqlTest public class MqttAttributesUpdatesSqlJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java index 4d46f3865b..68267bbd49 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.updates.sql; +package org.thingsboard.server.transport.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; @DaoSqlTest public class MqttAttributesUpdatesSqlProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java index 36da5466cb..6bbcf82e39 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim; +package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java similarity index 97% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java index 2b4e0d14df..97e177aef5 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim; +package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.junit.After; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java index 8f21c70f98..c3a5fb8cbb 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim; +package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java index a3d9ed886a..18d504f94c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim.sql; +package org.thingsboard.server.transport.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; +import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimJsonDeviceTest; @DaoSqlTest public class MqttClaimDeviceJsonSqlTest extends AbstractMqttClaimJsonDeviceTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java index 7a62ed897d..fd3595f543 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim.sql; +package org.thingsboard.server.transport.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest; +import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimProtoDeviceTest; @DaoSqlTest public class MqttClaimDeviceProtoSqlTest extends AbstractMqttClaimProtoDeviceTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceSqlTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceSqlTest.java index 64cd7f5481..666c51394d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceSqlTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.claim.sql; +package org.thingsboard.server.transport.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest; +import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest; @DaoSqlTest public class MqttClaimDeviceSqlTest extends AbstractMqttClaimDeviceTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 1011c86b7f..106c801676 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.provision; +package org.thingsboard.server.transport.mqtt.provision; import com.google.gson.JsonObject; import io.netty.handler.codec.mqtt.MqttQoS; @@ -38,7 +38,7 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 476b4add4c..53fde9412b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.provision; +package org.thingsboard.server.transport.mqtt.provision; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; @@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRespo import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java similarity index 83% rename from application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java index 82a797bcf6..bca75c87b2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.provision.sql; +package org.thingsboard.server.transport.mqtt.provision.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; +import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; @DaoSqlTest public class MqttProvisionDeviceJsonSqlTest extends AbstractMqttProvisionJsonDeviceTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java similarity index 83% rename from application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java index 792c41c7ce..62cb3d4779 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.provision.sql; +package org.thingsboard.server.transport.mqtt.provision.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; +import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; @DaoSqlTest public class MqttProvisionDeviceProtoSqlTest extends AbstractMqttProvisionProtoDeviceTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java index 7ccc14b65d..32f770a41c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc; +package org.thingsboard.server.transport.mqtt.rpc; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.extern.slf4j.Slf4j; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index 19e74b6fdc..90de461627 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc; +package org.thingsboard.server.transport.mqtt.rpc; import com.fasterxml.jackson.databind.JsonNode; import com.google.protobuf.InvalidProtocolBufferException; @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.Arrays; import java.util.concurrent.CountDownLatch; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java similarity index 97% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java index fefae730ae..4b2f38cbd9 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc; +package org.thingsboard.server.transport.mqtt.rpc; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 4f73ed0b0b..575dfdb052 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc; +package org.thingsboard.server.transport.mqtt.rpc; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java index 45be4b88b6..c17570b5ca 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc.sql; +package org.thingsboard.server.transport.mqtt.rpc.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest; @DaoSqlTest public class MqttServerSideRpcJsonSqlIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java index 6819f87dce..fa21510bb1 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc.sql; +package org.thingsboard.server.transport.mqtt.rpc.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java index 114fa67380..816ac7780d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.rpc.sql; +package org.thingsboard.server.transport.mqtt.rpc.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest; +import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index 874d55606d..7c646f2276 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes; +package org.thingsboard.server.transport.mqtt.telemetry.attributes; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -25,7 +25,7 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.Arrays; import java.util.HashSet; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java similarity index 96% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java index b6b9a745d8..7a9d52e2ca 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes; +package org.thingsboard.server.transport.mqtt.telemetry.attributes; import lombok.extern.slf4j.Slf4j; import org.junit.After; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index 4670946a60..2fb594a62c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes; +package org.thingsboard.server.transport.mqtt.telemetry.attributes; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.google.protobuf.Descriptors; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java index 0d3b9ee235..c3dafc4eef 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes.sql; +package org.thingsboard.server.transport.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; @DaoSqlTest public class MqttAttributesSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java index 9cd4659271..306b89ad23 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes.sql; +package org.thingsboard.server.transport.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; @DaoSqlTest public class MqttAttributesSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java index 38ba5c4d18..57376961db 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.attributes.sql; +package org.thingsboard.server.transport.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoSqlTest public class MqttAttributesSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java similarity index 98% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index 9ecc3415c2..f3cb3789b9 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries; import com.fasterxml.jackson.core.type.TypeReference; import io.netty.handler.codec.mqtt.MqttQoS; @@ -26,12 +26,11 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import java.util.Arrays; import java.util.HashSet; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java similarity index 97% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java index a006d15ac6..2e685415b9 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 12898a4a68..93a004318d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.google.protobuf.Descriptors; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java similarity index 82% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java index 2dbd8aaf41..67946ffc6b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.nosql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java index eca09478db..6b9987091e 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.nosql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; @DaoNoSqlTest public class MqttTimeseriesNoSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java similarity index 81% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java index 67f7c61352..dfdae8d3f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.nosql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; @DaoNoSqlTest public class MqttTimeseriesNoSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java similarity index 82% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java index 844ae272f5..e89b45fc32 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.sql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java similarity index 82% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java index 943f5b7877..4f4fd84168 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.sql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java similarity index 82% rename from application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java index bf609bf9be..cf227e6af4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.telemetry.timeseries.sql; +package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; +import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index 278fed3ccc..14f6572697 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.alarm; -import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.EntityId; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java index 289fc36c07..1e67655ae4 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.widget; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import java.util.List; @@ -25,12 +27,18 @@ public interface WidgetTypeService { WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId); - WidgetType saveWidgetType(WidgetType widgetType); + WidgetTypeDetails findWidgetTypeDetailsById(TenantId tenantId, WidgetTypeId widgetTypeId); + + WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetType); void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId); List findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); + List findWidgetTypesDetailsByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); + + List findWidgetTypesInfosByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); + WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias); void deleteWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CoapDeviceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/CoapDeviceType.java new file mode 100644 index 0000000000..64b6c1f946 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CoapDeviceType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum CoapDeviceType { + DEFAULT, + EFENTO +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java index 9b9a021fcf..6409ba4e90 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data; public enum DeviceTransportType { DEFAULT, MQTT, - LWM2M + LWM2M, + COAP } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java new file mode 100644 index 0000000000..96aef40788 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.data; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.thingsboard.server.common.data.DeviceTransportType; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class CoapDeviceTransportConfiguration implements DeviceTransportConfiguration { + + @JsonIgnore + private Map properties = new HashMap<>(); + + @JsonAnyGetter + public Map properties() { + return this.properties; + } + + @JsonAnySetter + public void put(String name, Object value) { + this.properties.put(name, value); + } + + @Override + public DeviceTransportType getType() { + return DeviceTransportType.COAP; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index 5409dcd339..2df6bf5413 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -29,7 +29,8 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), - @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M")}) + @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), + @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP")}) public interface DeviceTransportConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceProfileTransportConfiguration.java new file mode 100644 index 0000000000..4445f8e149 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceProfileTransportConfiguration.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceTransportType; + +@Data +public class CoapDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { + + private CoapDeviceTypeConfiguration coapDeviceTypeConfiguration; + + @Override + public DeviceTransportType getType() { + return DeviceTransportType.COAP; + } + + public CoapDeviceTypeConfiguration getCoapDeviceTypeConfiguration() { + if (coapDeviceTypeConfiguration != null) { + return coapDeviceTypeConfiguration; + } else { + return new DefaultCoapDeviceTypeConfiguration(); + } + } +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java new file mode 100644 index 0000000000..c47dd79368 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.CoapDeviceType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "coapDeviceType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DefaultCoapDeviceTypeConfiguration.class, name = "DEFAULT"), + @JsonSubTypes.Type(value = EfentoCoapDeviceTypeConfiguration.class, name = "EFENTO")}) +public interface CoapDeviceTypeConfiguration { + + @JsonIgnore + CoapDeviceType getCoapDeviceType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java new file mode 100644 index 0000000000..dc6759a214 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.CoapDeviceType; + +@Data +public class DefaultCoapDeviceTypeConfiguration implements CoapDeviceTypeConfiguration { + + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + + @Override + public CoapDeviceType getCoapDeviceType() { + return CoapDeviceType.DEFAULT; + } + + public TransportPayloadTypeConfiguration getTransportPayloadTypeConfiguration() { + if (transportPayloadTypeConfiguration != null) { + return transportPayloadTypeConfiguration; + } else { + return new JsonTransportPayloadConfiguration(); + } + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 710f32e195..d5ec9d2136 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -31,7 +31,8 @@ import java.io.Serializable; @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), - @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) + @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), + @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP")}) public interface DeviceProfileTransportConfiguration extends Serializable { @JsonIgnore diff --git a/application/src/test/java/org/thingsboard/server/mqtt/DbConfigurationTestRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java similarity index 65% rename from application/src/test/java/org/thingsboard/server/mqtt/DbConfigurationTestRule.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java index e9bf33340b..cc1832e2e6 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/DbConfigurationTestRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java @@ -13,19 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt; +package org.thingsboard.server.common.data.device.profile; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; +import lombok.Data; +import org.thingsboard.server.common.data.CoapDeviceType; -/** - * Created by ashvayka on 11.05.18. - */ -public class DbConfigurationTestRule implements TestRule { +@Data +public class EfentoCoapDeviceTypeConfiguration implements CoapDeviceTypeConfiguration { @Override - public Statement apply(Statement base, Description description) { - return null; + public CoapDeviceType getCoapDeviceType() { + return CoapDeviceType.EFENTO; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java new file mode 100644 index 0000000000..e3463f3614 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.widget; + +import lombok.Data; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetTypeId; + +@Data +public class BaseWidgetType extends BaseData implements HasTenantId { + + private static final long serialVersionUID = 8388684344603660756L; + + private TenantId tenantId; + private String bundleAlias; + private String alias; + private String name; + + public BaseWidgetType() { + super(); + } + + public BaseWidgetType(WidgetTypeId id) { + super(id); + } + + public BaseWidgetType(BaseWidgetType widgetType) { + super(widgetType); + this.tenantId = widgetType.getTenantId(); + this.bundleAlias = widgetType.getBundleAlias(); + this.alias = widgetType.getAlias(); + this.name = widgetType.getName(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java index 146ae3b9ce..04432093ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java @@ -16,21 +16,12 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.id.TenantId; +import lombok.Data; import org.thingsboard.server.common.data.id.WidgetTypeId; -@EqualsAndHashCode(callSuper = true) -public class WidgetType extends BaseData implements HasTenantId { +@Data +public class WidgetType extends BaseWidgetType { - private static final long serialVersionUID = 8388684344603660756L; - - private TenantId tenantId; - private String bundleAlias; - private String alias; - private String name; private transient JsonNode descriptor; public WidgetType() { @@ -41,65 +32,13 @@ public class WidgetType extends BaseData implements HasTenantId { super(id); } + public WidgetType(BaseWidgetType baseWidgetType) { + super(baseWidgetType); + } + public WidgetType(WidgetType widgetType) { super(widgetType); - this.tenantId = widgetType.getTenantId(); - this.bundleAlias = widgetType.getBundleAlias(); - this.alias = widgetType.getAlias(); - this.name = widgetType.getName(); this.descriptor = widgetType.getDescriptor(); } - public TenantId getTenantId() { - return tenantId; - } - - public void setTenantId(TenantId tenantId) { - this.tenantId = tenantId; - } - - public String getBundleAlias() { - return bundleAlias; - } - - public void setBundleAlias(String bundleAlias) { - this.bundleAlias = bundleAlias; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public JsonNode getDescriptor() { - return descriptor; - } - - public void setDescriptor(JsonNode descriptor) { - this.descriptor = descriptor; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("WidgetType{"); - sb.append("tenantId=").append(tenantId); - sb.append(", bundleAlias='").append(bundleAlias).append('\''); - sb.append(", alias='").append(alias).append('\''); - sb.append(", name='").append(name).append('\''); - sb.append(", descriptor=").append(descriptor); - sb.append('}'); - return sb.toString(); - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java new file mode 100644 index 0000000000..232a10e755 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.widget; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import org.thingsboard.server.common.data.id.WidgetTypeId; + +@Data +public class WidgetTypeDetails extends WidgetType { + + private String image; + private String description; + + public WidgetTypeDetails() { + super(); + } + + public WidgetTypeDetails(WidgetTypeId id) { + super(id); + } + + public WidgetTypeDetails(BaseWidgetType baseWidgetType) { + super(baseWidgetType); + } + + public WidgetTypeDetails(WidgetTypeDetails widgetTypeDetails) { + super(widgetTypeDetails); + this.image = widgetTypeDetails.getImage(); + this.description = widgetTypeDetails.getDescription(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java new file mode 100644 index 0000000000..645483bcc3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.widget; + +import lombok.Data; +import org.thingsboard.server.common.data.id.WidgetTypeId; + +@Data +public class WidgetTypeInfo extends BaseWidgetType { + + private String image; + private String description; + private String widgetType; + + public WidgetTypeInfo() { + super(); + } + + public WidgetTypeInfo(WidgetTypeId id) { + super(id); + } + + public WidgetTypeInfo(BaseWidgetType baseWidgetType) { + super(baseWidgetType); + } + + public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo) { + super(widgetTypeInfo); + this.image = widgetTypeInfo.getImage(); + this.description = widgetTypeInfo.getDescription(); + this.widgetType = widgetTypeInfo.getWidgetType(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 62869dad12..a15cd8baf5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -29,7 +29,8 @@ public class WidgetsBundle extends SearchTextBased implements H private TenantId tenantId; private String alias; private String title; - private byte[] image; + private String image; + private String description; public WidgetsBundle() { super(); @@ -45,6 +46,7 @@ public class WidgetsBundle extends SearchTextBased implements H this.alias = widgetsBundle.getAlias(); this.title = widgetsBundle.getTitle(); this.image = widgetsBundle.getImage(); + this.description = widgetsBundle.getDescription(); } public TenantId getTenantId() { @@ -71,14 +73,18 @@ public class WidgetsBundle extends SearchTextBased implements H this.title = title; } - public byte[] getImage() { + public String getImage() { return image; } - public void setImage(byte[] image) { + public void setImage(String image) { this.image = image; } + public String getDescription() { return description; } + + public void setDescription(String description) { this.description = description; } + @Override public String getSearchText() { return getTitle(); @@ -90,7 +96,8 @@ public class WidgetsBundle extends SearchTextBased implements H result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0); result = 31 * result + (alias != null ? alias.hashCode() : 0); result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + Arrays.hashCode(image); + result = 31 * result + (image != null ? image.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); return result; } @@ -105,7 +112,9 @@ public class WidgetsBundle extends SearchTextBased implements H if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false; if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false; if (title != null ? !title.equals(that.title) : that.title != null) return false; - return Arrays.equals(image, that.image); + if (image != null ? !image.equals(that.image) : that.image != null) return false; + if (description != null ? !description.equals(that.image) : that.description != null) return false; + return true; } @Override @@ -114,7 +123,8 @@ public class WidgetsBundle extends SearchTextBased implements H sb.append("tenantId=").append(tenantId); sb.append(", alias='").append(alias).append('\''); sb.append(", title='").append(title).append('\''); - sb.append(", image=").append(Arrays.toString(image)); + sb.append(", image='").append(image).append('\''); + sb.append(", description='").append(description).append('\''); sb.append('}'); return sb.toString(); } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 2b40259238..cf08fc4e75 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -404,6 +404,7 @@ message TbTimeSeriesSubscriptionProto { repeated TbSubscriptionKetStateProto keyStates = 3; int64 startTime = 4; int64 endTime = 5; + bool latestValues = 6; } message TbAttributeSubscriptionProto { diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index cea8ddc0ce..958f20337f 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -83,6 +83,19 @@ mockito-core test + + com.google.protobuf + protobuf-java + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/AbstractCoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/AbstractCoapTransportResource.java new file mode 100644 index 0000000000..85a4f5ba70 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/AbstractCoapTransportResource.java @@ -0,0 +1,140 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.SessionInfoCreator; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.UUID; +import java.util.function.BiConsumer; + +@Slf4j +public abstract class AbstractCoapTransportResource extends CoapResource { + + protected final CoapTransportContext transportContext; + protected final TransportService transportService; + + public AbstractCoapTransportResource(CoapTransportContext context, String name) { + super(name); + this.transportContext = context; + this.transportService = context.getTransportService(); + } + + @Override + public void handleGET(CoapExchange exchange) { + processHandleGet(exchange); + } + + @Override + public void handlePOST(CoapExchange exchange) { + processHandlePost(exchange); + } + + protected abstract void processHandleGet(CoapExchange exchange); + + protected abstract void processHandlePost(CoapExchange exchange); + + protected void reportActivity(TransportProtos.SessionInfoProto sessionInfo, boolean hasAttributeSubscription, boolean hasRpcSubscription) { + transportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(hasAttributeSubscription) + .setRpcSubscription(hasRpcSubscription) + .setLastActivityTime(System.currentTimeMillis()) + .build(), TransportServiceCallback.EMPTY); + } + + protected static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { + return TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + } + + public static class CoapDeviceAuthCallback implements TransportServiceCallback { + private final TransportContext transportContext; + private final CoapExchange exchange; + private final BiConsumer onSuccess; + + public CoapDeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, BiConsumer onSuccess) { + this.transportContext = transportContext; + this.exchange = exchange; + this.onSuccess = onSuccess; + } + + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, transportContext, UUID.randomUUID()); + onSuccess.accept(sessionInfoProto, deviceProfile); + } else { + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + public static class CoapOkCallback implements TransportServiceCallback { + private final CoapExchange exchange; + private final CoAP.ResponseCode onSuccessResponse; + private final CoAP.ResponseCode onFailureResponse; + + public CoapOkCallback(CoapExchange exchange, CoAP.ResponseCode onSuccessResponse, CoAP.ResponseCode onFailureResponse) { + this.exchange = exchange; + this.onSuccessResponse = onSuccessResponse; + this.onFailureResponse = onFailureResponse; + } + + @Override + public void onSuccess(Void msg) { + exchange.respond(onSuccessResponse); + } + + @Override + public void onError(Throwable e) { + exchange.respond(onFailureResponse); + } + } + + public static class CoapNoOpCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapNoOpCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + } + + @Override + public void onError(Throwable e) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java index 589ae3d41e..8cd117e99c 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java @@ -22,7 +22,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; +import org.thingsboard.server.transport.coap.efento.adaptor.EfentoCoapAdaptor; +import org.thingsboard.server.transport.coap.adaptors.JsonCoapAdaptor; +import org.thingsboard.server.transport.coap.adaptors.ProtoCoapAdaptor; /** @@ -47,6 +49,14 @@ public class CoapTransportContext extends TransportContext { @Getter @Autowired - private CoapTransportAdaptor adaptor; + private JsonCoapAdaptor jsonCoapAdaptor; + + @Getter + @Autowired + private ProtoCoapAdaptor protoCoapAdaptor; + + @Getter + @Autowired + private EfentoCoapAdaptor efentoCoapAdaptor; } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 9332502207..d91b1dc214 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -15,32 +15,39 @@ */ package org.thingsboard.server.transport.coap; +import com.google.gson.JsonParseException; +import com.google.protobuf.Descriptors; +import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapObserveRelation; -import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; -import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.core.server.resources.ResourceObserver; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.SessionInfoCreator; -import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; import java.util.List; import java.util.Optional; @@ -52,61 +59,60 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; @Slf4j -public class CoapTransportResource extends CoapResource { +public class CoapTransportResource extends AbstractCoapTransportResource { private static final int ACCESS_TOKEN_POSITION = 3; private static final int FEATURE_TYPE_POSITION = 4; private static final int REQUEST_ID_POSITION = 5; - private final CoapTransportContext transportContext; - private final TransportService transportService; - private final long timeout; private final ConcurrentMap tokenToSessionIdMap = new ConcurrentHashMap<>(); + private final ConcurrentMap tokenToNotificationCounterMap = new ConcurrentHashMap<>(); private final Set rpcSubscriptions = ConcurrentHashMap.newKeySet(); private final Set attributeSubscriptions = ConcurrentHashMap.newKeySet(); - public CoapTransportResource(CoapTransportContext context, String name) { - super(name); - this.transportContext = context; - this.transportService = context.getTransportService(); - this.timeout = context.getTimeout(); - // This is important to turn off existing observable logic in - // CoapResource. We will have our own observe monitoring due to 1:1 - // observe relationship. + public CoapTransportResource(CoapTransportContext coapTransportContext, String name) { + super(coapTransportContext, name); this.setObservable(true); // enable observing - this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs - this.getAttributes().setObservable(); // mark observable in the Link-Format - // schedule a periodic update task, otherwise let events call changed() - Timer timer = new Timer(); - timer.schedule(new UpdateTask(), 0, 5000); + this.addObserver(new CoapResourceObserver()); +// this.setObservable(false); // disable observing +// this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs +// this.getAttributes().setObservable(); // mark observable in the Link-Format } - private class UpdateTask extends TimerTask { - @Override - public void run() { - // .. periodic update of the resource - changed(); // notify all observers + public void checkObserveRelation(Exchange exchange, Response response) { + String token = getTokenFromRequest(exchange.getRequest()); + final ObserveRelation relation = exchange.getRelation(); + if (relation == null || relation.isCanceled()) { + return; // because request did not try to establish a relation } + if (CoAP.ResponseCode.isSuccess(response.getCode())) { + + if (!relation.isEstablished()) { + relation.setEstablished(); + addObserveRelation(relation); + } + AtomicInteger notificationCounter = tokenToNotificationCounterMap.computeIfAbsent(token, s -> new AtomicInteger(0)); + response.getOptions().setObserve(notificationCounter.getAndIncrement()); + } // ObserveLayer takes care of the else case } @Override - public void handleGET(CoapExchange exchange) { + protected void processHandleGet(CoapExchange exchange) { Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { + if (featureType.isEmpty()) { log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } else if (featureType.get() == FeatureType.TELEMETRY) { log.trace("Can't fetch/subscribe to timeseries updates"); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } else if (exchange.getRequestOptions().hasObserve()) { processExchangeGetRequest(exchange, featureType.get()); } else if (featureType.get() == FeatureType.ATTRIBUTES) { processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); } else { log.trace("Invalid feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } } @@ -122,11 +128,11 @@ public class CoapTransportResource extends CoapResource { } @Override - public void handlePOST(CoapExchange exchange) { + protected void processHandlePost(CoapExchange exchange) { Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { + if (featureType.isEmpty()) { log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } else { switch (featureType.get()) { case ATTRIBUTES: @@ -154,14 +160,27 @@ public class CoapTransportResource extends CoapResource { } private void processProvision(CoapExchange exchange) { - log.trace("Processing {}", exchange.advanced().getRequest()); exchange.accept(); try { - transportService.process(transportContext.getAdaptor().convertToProvisionRequestMsg(UUID.randomUUID(), exchange.advanced().getRequest()), - new DeviceProvisionCallback(exchange)); + UUID sessionId = UUID.randomUUID(); + log.trace("[{}] Processing provision publish msg [{}]!", sessionId, exchange.advanced().getRequest()); + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg; + TransportPayloadType payloadType; + try { + provisionRequestMsg = transportContext.getJsonCoapAdaptor().convertToProvisionRequestMsg(sessionId, exchange.advanced().getRequest()); + payloadType = TransportPayloadType.JSON; + } catch (Exception e) { + if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { + provisionRequestMsg = transportContext.getProtoCoapAdaptor().convertToProvisionRequestMsg(sessionId, exchange.advanced().getRequest()); + payloadType = TransportPayloadType.PROTOBUF; + } else { + throw new AdaptorException(e); + } + } + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(exchange, payloadType)); } catch (AdaptorException e) { log.trace("Failed to decode message: ", e); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } } @@ -172,119 +191,125 @@ public class CoapTransportResource extends CoapResource { Request request = advanced.getRequest(); Optional credentials = decodeCredentials(request); - if (!credentials.isPresent()) { - exchange.respond(ResponseCode.BAD_REQUEST); + if (credentials.isEmpty()) { + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); return; } - transportService.process(DeviceTransportType.DEFAULT, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), - new DeviceAuthCallback(transportContext, exchange, sessionInfo -> { + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), + new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); try { + TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile); + CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload()); switch (type) { case POST_ATTRIBUTES_REQUEST: transportService.process(sessionInfo, - transportContext.getAdaptor().convertToPostAttributes(sessionId, request), - new CoapOkCallback(exchange)); - reportActivity(sessionId, sessionInfo); + coapTransportAdaptor.convertToPostAttributes(sessionId, request, + transportConfigurationContainer.getAttributesMsgDescriptor()), + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId)); break; case POST_TELEMETRY_REQUEST: transportService.process(sessionInfo, - transportContext.getAdaptor().convertToPostTelemetry(sessionId, request), - new CoapOkCallback(exchange)); - reportActivity(sessionId, sessionInfo); + coapTransportAdaptor.convertToPostTelemetry(sessionId, request, + transportConfigurationContainer.getTelemetryMsgDescriptor()), + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId)); break; case CLAIM_REQUEST: transportService.process(sessionInfo, - transportContext.getAdaptor().convertToClaimDevice(sessionId, request, sessionInfo), - new CoapOkCallback(exchange)); + coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo), + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); break; case SUBSCRIBE_ATTRIBUTES_REQUEST: - transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); - transportService.process(sessionInfo, - TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), - new CoapNoOpCallback(exchange)); + TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request)); + if (currentAttrSession == null) { + attributeSubscriptions.add(sessionId); + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request)); + transportService.process(sessionInfo, + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange)); + } break; case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - attributeSubscriptions.remove(sessionId); - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request); + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); if (attrSession != null) { + UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB()); + attributeSubscriptions.remove(attrSessionId); transportService.process(attrSession, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), - new CoapOkCallback(exchange)); - closeAndDeregister(sessionInfo); + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + closeAndDeregister(sessionInfo, sessionId); } break; case SUBSCRIBE_RPC_COMMANDS_REQUEST: - rpcSubscriptions.add(sessionId); - transportService.process(sessionInfo, - TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), - new CoapNoOpCallback(exchange)); + TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request)); + if (currentRpcSession == null) { + rpcSubscriptions.add(sessionId); + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request)); + transportService.process(sessionInfo, + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), + new CoapNoOpCallback(exchange)); + } else { + UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB()); + reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId)); + } break; case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - rpcSubscriptions.remove(sessionId); - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request); + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); if (rpcSession != null) { + UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB()); + rpcSubscriptions.remove(rpcSessionId); transportService.process(rpcSession, TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), - new CoapOkCallback(exchange)); - closeAndDeregister(sessionInfo); + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + closeAndDeregister(sessionInfo, sessionId); } break; case TO_DEVICE_RPC_RESPONSE: transportService.process(sessionInfo, - transportContext.getAdaptor().convertToDeviceRpcResponse(sessionId, request), - new CoapOkCallback(exchange)); + coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request), + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); break; case TO_SERVER_RPC_REQUEST: - transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout()); transportService.process(sessionInfo, - transportContext.getAdaptor().convertToServerRpcRequest(sessionId, request), + coapTransportAdaptor.convertToServerRpcRequest(sessionId, request), new CoapNoOpCallback(exchange)); break; case GET_ATTRIBUTES_REQUEST: - transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout()); transportService.process(sessionInfo, - transportContext.getAdaptor().convertToGetAttributes(sessionId, request), + coapTransportAdaptor.convertToGetAttributes(sessionId, request), new CoapNoOpCallback(exchange)); break; } } catch (AdaptorException e) { log.trace("[{}] Failed to decode message: ", sessionId, e); - exchange.respond(ResponseCode.BAD_REQUEST); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } })); } - private void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { - transportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(attributeSubscriptions.contains(sessionId)) - .setRpcSubscription(rpcSubscriptions.contains(sessionId)) - .setLastActivityTime(System.currentTimeMillis()) - .build(), TransportServiceCallback.EMPTY); - } - - private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { - EndpointContext sourceContext = request.getSourceContext(); - String token = sourceContext.getPeerAddress().getAddress().getHostAddress() + ":" + sourceContext.getPeerAddress().getPort() + ":" + request.getTokenString(); + private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { + tokenToNotificationCounterMap.remove(token); return tokenToSessionIdMap.remove(token); } - private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) { - EndpointContext sourceContext = request.getSourceContext(); - String token = sourceContext.getPeerAddress().getAddress().getHostAddress() + ":" + sourceContext.getPeerAddress().getPort() + ":" + request.getTokenString(); + private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, String token) { tokenToSessionIdMap.putIfAbsent(token, sessionInfo); - CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange); - transportService.registerAsyncSession(sessionInfo, attrListener); + transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor)); transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); - return token; } - private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { - return TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) - .setEvent(event).build(); + private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) { + return new CoapSessionListener(exchange, coapTransportAdaptor); + } + + private String getTokenFromRequest(Request request) { + return (request.getSourceContext() != null ? request.getSourceContext().getPeerAddress().getAddress().getHostAddress() : "null") + + ":" + (request.getSourceContext() != null ? request.getSourceContext().getPeerAddress().getPort() : -1) + ":" + request.getTokenString(); } private Optional decodeCredentials(Request request) { @@ -327,167 +352,192 @@ public class CoapTransportResource extends CoapResource { return this; } - private static class DeviceAuthCallback implements TransportServiceCallback { - private final TransportContext transportContext; + private static class DeviceProvisionCallback implements TransportServiceCallback { private final CoapExchange exchange; - private final Consumer onSuccess; + private final TransportPayloadType payloadType; - DeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, Consumer onSuccess) { - this.transportContext = transportContext; + DeviceProvisionCallback(CoapExchange exchange, TransportPayloadType payloadType) { this.exchange = exchange; - this.onSuccess = onSuccess; + this.payloadType = payloadType; } @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (msg.hasDeviceInfo()) { - onSuccess.accept(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID())); + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) { + CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED; + if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) { + responseCode = CoAP.ResponseCode.BAD_REQUEST; + } + if (payloadType.equals(TransportPayloadType.JSON)) { + exchange.respond(responseCode, JsonConverter.toJson(msg).toString()); } else { - exchange.respond(ResponseCode.UNAUTHORIZED); + exchange.respond(responseCode, msg.toByteArray()); } } @Override public void onError(Throwable e) { log.warn("Failed to process request", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); } } - private static class DeviceProvisionCallback implements TransportServiceCallback { - private final CoapExchange exchange; - - DeviceProvisionCallback(CoapExchange exchange) { - this.exchange = exchange; - } + private static class CoapSessionListener implements SessionMsgListener { - @Override - public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) { - exchange.respond(JsonConverter.toJson(msg).toString()); - } - - @Override - public void onError(Throwable e) { - log.warn("Failed to process request", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - - private static class CoapOkCallback implements TransportServiceCallback { private final CoapExchange exchange; + private final CoapTransportAdaptor coapTransportAdaptor; - CoapOkCallback(CoapExchange exchange) { - this.exchange = exchange; - } - - @Override - public void onSuccess(Void msg) { - exchange.respond(ResponseCode.VALID); - } - - @Override - public void onError(Throwable e) { - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - - private static class CoapNoOpCallback implements TransportServiceCallback { - private final CoapExchange exchange; - - CoapNoOpCallback(CoapExchange exchange) { - this.exchange = exchange; - } - - @Override - public void onSuccess(Void msg) { - - } - - @Override - public void onError(Throwable e) { - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - - public class CoapSessionListener implements SessionMsgListener { - - private final CoapExchange exchange; - private final AtomicInteger seqNumber = new AtomicInteger(2); - - CoapSessionListener(UUID sessionId, CoapExchange exchange) { + CoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) { this.exchange = exchange; + this.coapTransportAdaptor = coapTransportAdaptor; } @Override public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg msg) { try { - exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + exchange.respond(coapTransportAdaptor.convertToPublish(msg)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); } } @Override public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { try { - exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); } } @Override public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { - exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); + exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE); } @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { try { - exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); } } @Override public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg msg) { try { - exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + exchange.respond(coapTransportAdaptor.convertToPublish(msg)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); } } - public int getNextSeqNumber() { - return seqNumber.getAndIncrement(); + private boolean isConRequest() { + return exchange.advanced().getRequest().isConfirmable(); } } - public class CoapExchangeObserverProxy extends CoapObserveRelation { - - /** - * Constructs a new CoapObserveRelation with the specified request. - * - * @param request the request - * @param endpoint the endpoint - * @param executor - */ - protected CoapExchangeObserverProxy(Request request, Endpoint endpoint, ScheduledThreadPoolExecutor executor) { - super(request, endpoint, executor); + public class CoapResourceObserver implements ResourceObserver { + + @Override + public void changedName(String old) { + } + + @Override + public void changedPath(String old) { + } + + @Override + public void addedChild(Resource child) { + } + + @Override + public void removedChild(Resource child) { + } + + @Override + public void addedObserveRelation(ObserveRelation relation) { + if (log.isTraceEnabled()) { + Request request = relation.getExchange().getRequest(); + log.trace("Added Observe relation for token: {}", getTokenFromRequest(request)); + } + } + + @Override + public void removedObserveRelation(ObserveRelation relation) { + Request request = relation.getExchange().getRequest(); + String tokenFromRequest = getTokenFromRequest(request); + log.trace("Relation removed for token: {}", tokenFromRequest); + TransportProtos.SessionInfoProto sessionInfoToRemove = lookupAsyncSessionInfo(tokenFromRequest); + if (sessionInfoToRemove != null) { + closeAndDeregister(sessionInfoToRemove, new UUID(sessionInfoToRemove.getSessionIdMSB(), sessionInfoToRemove.getDeviceIdLSB())); + } } } - private void closeAndDeregister(TransportProtos.SessionInfoProto session) { + private void closeAndDeregister(TransportProtos.SessionInfoProto session, UUID sessionId) { transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); transportService.deregisterSession(session); - UUID sessionId = new UUID(session.getSessionIdMSB(), session.getSessionIdLSB()); rpcSubscriptions.remove(sessionId); attributeSubscriptions.remove(sessionId); } + private TransportConfigurationContainer getTransportConfigurationContainer(DeviceProfile deviceProfile) throws AdaptorException { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof DefaultDeviceProfileTransportConfiguration) { + return new TransportConfigurationContainer(true); + } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = + (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = + coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = + (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = + defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + if (transportPayloadTypeConfiguration instanceof JsonTransportPayloadConfiguration) { + return new TransportConfigurationContainer(true); + } else { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = + (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + String deviceAttributesProtoSchema = protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema(); + return new TransportConfigurationContainer(false, + protoTransportPayloadConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema), + protoTransportPayloadConfiguration.getAttributesDynamicMessageDescriptor(deviceAttributesProtoSchema)); + } + } else { + throw new AdaptorException("Invalid CoapDeviceTypeConfiguration type: " + coapDeviceTypeConfiguration.getClass().getSimpleName() + "!"); + } + } else { + throw new AdaptorException("Invalid DeviceProfileTransportConfiguration type" + transportConfiguration.getClass().getSimpleName() + "!"); + } + } + + private CoapTransportAdaptor getCoapTransportAdaptor(boolean jsonPayloadType) { + return jsonPayloadType ? transportContext.getJsonCoapAdaptor() : transportContext.getProtoCoapAdaptor(); + } + + @Data + private static class TransportConfigurationContainer { + + private boolean jsonPayload; + private Descriptors.Descriptor telemetryMsgDescriptor; + private Descriptors.Descriptor attributesMsgDescriptor; + + public TransportConfigurationContainer(boolean jsonPayload, Descriptors.Descriptor telemetryMsgDescriptor, Descriptors.Descriptor attributesMsgDescriptor) { + this.jsonPayload = jsonPayload; + this.telemetryMsgDescriptor = telemetryMsgDescriptor; + this.attributesMsgDescriptor = attributesMsgDescriptor; + } + + public TransportConfigurationContainer(boolean jsonPayload) { + this.jsonPayload = jsonPayload; + } + } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index 8abc859b21..de242fc7dd 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -20,10 +20,11 @@ import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.network.CoapEndpoint.Builder; +import org.eclipse.californium.core.server.resources.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; +import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -38,6 +39,8 @@ public class CoapTransportService { private static final String V1 = "v1"; private static final String API = "api"; + private static final String EFENTO = "efento"; + private static final String MEASUREMENTS = "m"; @Autowired private CoapTransportContext coapTransportContext; @@ -48,13 +51,19 @@ public class CoapTransportService { public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); log.info("Starting CoAP transport server"); + this.server = new CoapServer(); createResources(); + Resource root = this.server.getRoot(); + TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); + this.server.setMessageDeliverer(messageDeliverer); + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); - Builder builder = new Builder(); - builder.setInetSocketAddress(sockAddr); - CoapEndpoint coapEndpoint = builder.build(); + + CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder(); + coapEndpoitBuilder.setInetSocketAddress(sockAddr); + CoapEndpoint coapEndpoint = coapEndpoitBuilder.build(); server.addEndpoint(coapEndpoint); server.start(); @@ -64,7 +73,13 @@ public class CoapTransportService { private void createResources() { CoapResource api = new CoapResource(API); api.add(new CoapTransportResource(coapTransportContext, V1)); + + CoapResource efento = new CoapResource(EFENTO); + CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); + efento.add(efentoMeasurementsTransportResource); + server.add(api); + server.add(efento); } @PreDestroy diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapServerMessageDeliverer.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapServerMessageDeliverer.java new file mode 100644 index 0000000000..fa189e35f1 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapServerMessageDeliverer.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.OptionSet; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.server.ServerMessageDeliverer; +import org.eclipse.californium.core.server.resources.Resource; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +@Slf4j +public class TbCoapServerMessageDeliverer extends ServerMessageDeliverer { + + public TbCoapServerMessageDeliverer(Resource root) { + super(root); + } + + @Override + protected Resource findResource(Exchange exchange) { + validateUriPath(exchange); + return findResource(exchange.getRequest().getOptions().getUriPath()); + } + + private void validateUriPath(Exchange exchange) { + OptionSet options = exchange.getRequest().getOptions(); + List uriPathList = options.getUriPath(); + String path = toPath(uriPathList); + if (path != null) { + options.setUriPath(path); + exchange.getRequest().setOptions(options); + } + } + + private String toPath(List list) { + if (!CollectionUtils.isEmpty(list) && list.size() == 1) { + final String slash = "/"; + String path = list.get(0); + if (path.startsWith(slash)) { + path = path.substring(slash.length()); + } + return path; + } + return null; + } + +} \ No newline at end of file diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java new file mode 100644 index 0000000000..f7572d0506 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.adaptors; + +import org.eclipse.californium.core.coap.Request; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CoapAdaptorUtils { + + public static TransportProtos.GetAttributeRequestMsg toGetAttributeRequestMsg(Request inbound) throws AdaptorException { + List queryElements = inbound.getOptions().getUriQuery(); + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + if (queryElements != null && queryElements.size() > 0) { + Set clientKeys = toKeys(queryElements, "clientKeys"); + Set sharedKeys = toKeys(queryElements, "sharedKeys"); + if (clientKeys != null) { + result.addAllClientAttributeNames(clientKeys); + } + if (sharedKeys != null) { + result.addAllSharedAttributeNames(sharedKeys); + } + } + return result.build(); + } + + private static Set toKeys(List queryElements, String attributeName) throws AdaptorException { + String keys = null; + for (String queryElement : queryElements) { + String[] queryItem = queryElement.split("="); + if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { + keys = queryItem[1]; + } + } + if (keys != null && !StringUtils.isEmpty(keys)) { + return new HashSet<>(Arrays.asList(keys.split(","))); + } else { + return null; + } + } +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java index 677784d2cb..1d4703ead4 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -15,20 +15,20 @@ */ package org.thingsboard.server.transport.coap.adaptors; +import com.google.protobuf.Descriptors; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; -import org.thingsboard.server.transport.coap.CoapTransportResource; import java.util.UUID; public interface CoapTransportAdaptor { - TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException; + TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound, Descriptors.Descriptor telemetryMsgDescriptor) throws AdaptorException; - TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException; + TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound, Descriptors.Descriptor attributesMsgDescriptor) throws AdaptorException; TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException; @@ -38,13 +38,13 @@ public interface CoapTransportAdaptor { TransportProtos.ClaimDeviceMsg convertToClaimDevice(UUID sessionId, Request inbound, TransportProtos.SessionInfoProto sessionInfo) throws AdaptorException; - Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException; + Response convertToPublish(TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException; - Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; + Response convertToPublish(boolean isConfirmable, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; - Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; - Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; + Response convertToPublish(TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index 24429932cf..e382b2a3ee 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -19,32 +19,27 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; +import com.google.protobuf.Descriptors; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.CoapTransportResource; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.UUID; -@Component("JsonCoapAdaptor") +@Component @Slf4j public class JsonCoapAdaptor implements CoapTransportAdaptor { @Override - public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException { + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound, Descriptors.Descriptor telemetryMsgDescriptor) throws AdaptorException { String payload = validatePayload(sessionId, inbound, false); try { return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); @@ -54,7 +49,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } @Override - public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException { + public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound, Descriptors.Descriptor attributesMsgDescriptor) throws AdaptorException { String payload = validatePayload(sessionId, inbound, false); try { return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); @@ -65,19 +60,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { @Override public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException { - List queryElements = inbound.getOptions().getUriQuery(); - TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); - if (queryElements != null && queryElements.size() > 0) { - Set clientKeys = toKeys(queryElements, "clientKeys"); - Set sharedKeys = toKeys(queryElements, "sharedKeys"); - if (clientKeys != null) { - result.addAllClientAttributeNames(clientKeys); - } - if (sharedKeys != null) { - result.addAllSharedAttributeNames(sharedKeys); - } - } - return result.build(); + return CoapAdaptorUtils.toGetAttributeRequestMsg(inbound); } @Override @@ -107,17 +90,17 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } @Override - public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { - return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg)); + public Response convertToPublish(boolean isConfirmable, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { + return getObserveNotification(isConfirmable, JsonConverter.toJson(msg)); } @Override - public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { - return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg, true)); + public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { + return getObserveNotification(isConfirmable, JsonConverter.toJson(msg, true)); } @Override - public Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { + public Response convertToPublish(TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { Response response = new Response(CoAP.ResponseCode.CONTENT); JsonElement result = JsonConverter.toJson(msg); response.setPayload(result.toString()); @@ -135,7 +118,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } @Override - public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { + public Response convertToPublish(TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) { return new Response(CoAP.ResponseCode.NOT_FOUND); } else { @@ -146,10 +129,10 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } } - private Response getObserveNotification(int seqNumber, JsonElement json) { + private Response getObserveNotification(boolean confirmable, JsonElement json) { Response response = new Response(CoAP.ResponseCode.CONTENT); - response.getOptions().setObserve(seqNumber); response.setPayload(json.toString()); + response.setConfirmable(confirmable); return response; } @@ -164,19 +147,4 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { return payload; } - private Set toKeys(List queryElements, String attributeName) throws AdaptorException { - String keys = null; - for (String queryElement : queryElements) { - String[] queryItem = queryElement.split("="); - if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { - keys = queryItem[1]; - } - } - if (keys != null && !StringUtils.isEmpty(keys)) { - return new HashSet<>(Arrays.asList(keys.split(","))); - } else { - return null; - } - } - } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java new file mode 100644 index 0000000000..d4c53fdc9a --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java @@ -0,0 +1,149 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.adaptors; + +import com.google.gson.JsonParser; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.transport.adaptor.ProtoConverter; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTransportResource; + +import java.util.Optional; +import java.util.UUID; + +@Component +@Slf4j +public class ProtoCoapAdaptor implements CoapTransportAdaptor { + + @Override + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound, Descriptors.Descriptor telemetryMsgDescriptor) throws AdaptorException { + try { + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(dynamicMsgToJson(inbound.getPayload(), telemetryMsgDescriptor))); + } catch (Exception e) { + throw new AdaptorException(e); + } + } + + @Override + public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound, Descriptors.Descriptor attributesMsgDescriptor) throws AdaptorException { + try { + return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(inbound.getPayload(), attributesMsgDescriptor))); + } catch (Exception e) { + throw new AdaptorException(e); + } + } + + @Override + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException { + return CoapAdaptorUtils.toGetAttributeRequestMsg(inbound); + } + + @Override + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { + Optional requestId = CoapTransportResource.getRequestId(inbound); + if (requestId.isEmpty()) { + throw new AdaptorException("Request id is missing!"); + } else { + try { + String payload = TransportProtos.ToDeviceRpcResponseMsg.parseFrom(inbound.getPayload()).getPayload(); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.get()) + .setPayload(payload).build(); + } catch (InvalidProtocolBufferException e) { + throw new AdaptorException(e); + } + } + } + + @Override + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException { + try { + return ProtoConverter.convertToServerRpcRequest(inbound.getPayload(), 0); + } catch (InvalidProtocolBufferException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(UUID sessionId, Request inbound, TransportProtos.SessionInfoProto sessionInfo) throws AdaptorException { + DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); + try { + return ProtoConverter.convertToClaimDeviceProto(deviceId, inbound.getPayload()); + } catch (InvalidProtocolBufferException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException { + try { + return ProtoConverter.convertToProvisionRequestMsg(inbound.getPayload()); + } catch (InvalidProtocolBufferException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public Response convertToPublish(boolean isConfirmable, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { + return getObserveNotification(isConfirmable, msg.toByteArray()); + } + + @Override + public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { + return getObserveNotification(isConfirmable, msg.toByteArray()); + } + + @Override + public Response convertToPublish(TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { + Response response = new Response(CoAP.ResponseCode.CONTENT); + response.setPayload(msg.toByteArray()); + return response; + } + + @Override + public Response convertToPublish(TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { + if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) { + return new Response(CoAP.ResponseCode.NOT_FOUND); + } else { + Response response = new Response(CoAP.ResponseCode.CONTENT); + response.setPayload(msg.toByteArray()); + return response; + } + } + + private Response getObserveNotification(boolean confirmable, byte[] notification) { + Response response = new Response(CoAP.ResponseCode.CONTENT); + response.setPayload(notification); + response.setConfirmable(confirmable); + return response; + } + + private String dynamicMsgToJson(byte[] bytes, Descriptors.Descriptor descriptor) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, bytes); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java deleted file mode 100644 index 0fc5b44c43..0000000000 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.client; - -import java.io.IOException; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.elements.exception.ConnectorException; -import org.thingsboard.server.common.msg.session.FeatureType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -@Slf4j -public class DeviceEmulator { - - public static final String SN = "SN-" + new Random().nextInt(1000); - public static final String MODEL = "Model " + new Random().nextInt(1000); - private static final ObjectMapper mapper = new ObjectMapper(); - - private final String host; - private final int port; - private final String token; - - private CoapClient attributesClient; - private CoapClient telemetryClient; - private CoapClient rpcClient; - private String[] keys; - private ExecutorService executor = Executors.newFixedThreadPool(1); - private AtomicInteger seq = new AtomicInteger(100); - - private DeviceEmulator(String host, int port, String token, String keys) { - this.host = host; - this.port = port; - this.token = token; - this.attributesClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.ATTRIBUTES)); - this.telemetryClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.TELEMETRY)); - this.rpcClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC)); - this.keys = (keys != null && !keys.isEmpty()) ? keys.split(",") : null; - } - - public void start() { - executor.submit(new Runnable() { - - @Override - public void run() { - try { - sendObserveRequest(rpcClient); - while (!Thread.interrupted()) { - sendRequest(attributesClient, createAttributesRequest()); - sendRequest(telemetryClient, createTelemetryRequest()); - Thread.sleep(1000); - } - } catch (Exception e) { - log.error("Error occurred while sending COAP requests", e); - } - } - - private void sendRequest(CoapClient client, JsonNode request) throws IOException, ConnectorException { - CoapResponse telemetryResponse = client.setTimeout((long) 60000).post(mapper.writeValueAsString(request), - MediaTypeRegistry.APPLICATION_JSON); - log.info("Response: {}, {}", telemetryResponse.getCode(), telemetryResponse.getResponseText()); - } - - private void sendObserveRequest(CoapClient client) throws JsonProcessingException { - client.observe(new CoapHandler() { - @Override - public void onLoad(CoapResponse coapResponse) { - log.info("Command: {}, {}", coapResponse.getCode(), coapResponse.getResponseText()); - try { - JsonNode node = mapper.readTree(coapResponse.getResponseText()); - int requestId = node.get("id").asInt(); - String method = node.get("method").asText(); - ObjectNode params = (ObjectNode) node.get("params"); - ObjectNode response = mapper.createObjectNode(); - response.put("id", requestId); - response.set("response", params); - log.info("Command Response: {}, {}", requestId, mapper.writeValueAsString(response)); - CoapClient commandResponseClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC)); - commandResponseClient.post(new CoapHandler() { - @Override - public void onLoad(CoapResponse response) { - log.info("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); - } - - @Override - public void onError() { - log.info("Command Response Ack Error, No connect"); - //Do nothing - } - }, mapper.writeValueAsString(response), MediaTypeRegistry.APPLICATION_JSON); - - } catch (IOException e) { - log.error("Error occurred while processing COAP response", e); - } - } - - @Override - public void onError() { - //Do nothing - } - }); - } - - }); - } - - private ObjectNode createAttributesRequest() { - ObjectNode element = mapper.createObjectNode(); - element.put("serialNumber", SN); - element.put("model", MODEL); - return element; - } - - private ArrayNode createTelemetryRequest() { - ArrayNode rootNode = mapper.createArrayNode(); - for (String key : keys) { - ObjectNode element = mapper.createObjectNode(); - element.put(key, seq.incrementAndGet()); - rootNode.add(element); - } - return rootNode; - } - - protected void stop() { - executor.shutdownNow(); - } - - public static void main(String args[]) { - if (args.length != 4) { - System.out.println("Usage: java -jar " + DeviceEmulator.class.getSimpleName() + ".jar host port device_token keys"); - } - /** - * DeviceEmulator(String host, int port, String token, String keys) - * args[]: - * host = "localhost", - * port = 0, - * token = "{Tokrn device from thingboard}"), kSzbDRGwaZqZ6Y25gTLF - * keys = "{Telemetry}" - * - */ - final DeviceEmulator emulator = new DeviceEmulator(args[0], Integer.parseInt(args[1]), args[2], args[3]); - emulator.start(); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - emulator.stop(); - } - }); - } - - private String getFeatureTokenUrl(String host, int port, String token, FeatureType featureType) { - return getBaseUrl(host, port) + token + "/" + featureType.name().toLowerCase(); - } - - private String getBaseUrl(String host, int port) { - return "coap://" + host + ":" + port + "/api/v1/"; - } - -} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java new file mode 100644 index 0000000000..03ed92da58 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java @@ -0,0 +1,239 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.efento; + +import com.google.gson.JsonObject; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos; +import org.thingsboard.server.gen.transport.coap.MeasurementsProtos; +import org.thingsboard.server.transport.coap.AbstractCoapTransportResource; +import org.thingsboard.server.transport.coap.CoapTransportContext; +import org.thingsboard.server.transport.coap.efento.utils.CoapEfentoUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class CoapEfentoTransportResource extends AbstractCoapTransportResource { + + private static final int MEASUREMENTS_POSITION = 2; + private static final String MEASUREMENTS = "m"; + + public CoapEfentoTransportResource(CoapTransportContext context, String name) { + super(context, name); + this.setObservable(true); // enable observing + this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs +// this.getAttributes().setObservable(); // mark observable in the Link-Format + } + + @Override + protected void processHandleGet(CoapExchange exchange) { + exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED); + } + + @Override + protected void processHandlePost(CoapExchange exchange) { + Exchange advanced = exchange.advanced(); + Request request = advanced.getRequest(); + List uriPath = request.getOptions().getUriPath(); + boolean validPath = uriPath.size() == MEASUREMENTS_POSITION && uriPath.get(1).equals(MEASUREMENTS); + if (!validPath) { + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); + return; + } + byte[] bytes = request.getPayload(); + try { + MeasurementsProtos.ProtoMeasurements protoMeasurements = MeasurementsProtos.ProtoMeasurements.parseFrom(bytes); + log.trace("Successfully parsed Efento ProtoMeasurements: [{}]", protoMeasurements.getCloudToken()); + String token = protoMeasurements.getCloudToken(); + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), + new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); + try { + validateEfentoTransportConfiguration(deviceProfile); + List efentoMeasurements = getEfentoMeasurements(protoMeasurements, sessionId); + transportService.process(sessionInfo, + transportContext.getEfentoCoapAdaptor().convertToPostTelemetry(sessionId, efentoMeasurements), + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + reportActivity(sessionInfo, false, false); + } catch (AdaptorException e) { + log.error("[{}] Failed to decode Efento ProtoMeasurements: ", sessionId, e); + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); + } + })); + } catch (Exception e) { + log.error("Failed to decode Efento ProtoMeasurements: ", e); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Resource getChild(String name) { + return this; + } + + private void validateEfentoTransportConfiguration(DeviceProfile deviceProfile) throws AdaptorException { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = + (CoapDeviceProfileTransportConfiguration) transportConfiguration; + if (!(coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration() instanceof EfentoCoapDeviceTypeConfiguration)) { + throw new AdaptorException("Invalid CoapDeviceTypeConfiguration type: " + coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration().getClass().getSimpleName() + "!"); + } + } else { + throw new AdaptorException("Invalid DeviceProfileTransportConfiguration type" + transportConfiguration.getClass().getSimpleName() + "!"); + } + } + + private List getEfentoMeasurements(MeasurementsProtos.ProtoMeasurements protoMeasurements, UUID sessionId) { + String serialNumber = CoapEfentoUtils.convertByteArrayToString(protoMeasurements.getSerialNum().toByteArray()); + boolean batteryStatus = protoMeasurements.getBatteryStatus(); + int measurementPeriodBase = protoMeasurements.getMeasurementPeriodBase(); + int measurementPeriodFactor = protoMeasurements.getMeasurementPeriodFactor(); + int signal = protoMeasurements.getSignal(); + List channelsList = protoMeasurements.getChannelsList(); + Map valuesMap = new TreeMap<>(); + if (!CollectionUtils.isEmpty(channelsList)) { + int channel = 0; + JsonObject values; + for (MeasurementsProtos.ProtoChannel protoChannel : channelsList) { + channel++; + boolean isBinarySensor = false; + MeasurementTypeProtos.MeasurementType measurementType = protoChannel.getType(); + String measurementTypeName = measurementType.name(); + if (measurementType.equals(MeasurementTypeProtos.MeasurementType.OK_ALARM) + || measurementType.equals(MeasurementTypeProtos.MeasurementType.FLOODING)) { + isBinarySensor = true; + } + if (measurementPeriodFactor == 0 && isBinarySensor) { + measurementPeriodFactor = 14; + } else { + measurementPeriodFactor = 1; + } + int measurementPeriod = measurementPeriodBase * measurementPeriodFactor; + long measurementPeriodMillis = TimeUnit.SECONDS.toMillis(measurementPeriod); + long nextTransmissionAtMillis = TimeUnit.SECONDS.toMillis(protoMeasurements.getNextTransmissionAt()); + int startPoint = protoChannel.getStartPoint(); + int startTimestamp = protoChannel.getTimestamp(); + long startTimestampMillis = TimeUnit.SECONDS.toMillis(startTimestamp); + List sampleOffsetsList = protoChannel.getSampleOffsetsList(); + if (!CollectionUtils.isEmpty(sampleOffsetsList)) { + int sampleOfssetsListSize = sampleOffsetsList.size(); + for (int i = 0; i < sampleOfssetsListSize; i++) { + int sampleOffset = sampleOffsetsList.get(i); + Integer previousSampleOffset = isBinarySensor && i > 0 ? sampleOffsetsList.get(i - 1) : null; + if (sampleOffset == -32768) { + log.warn("[{}],[{}] Sensor error value! Ignoring.", sessionId, sampleOffset); + } else { + switch (measurementType) { + case TEMPERATURE: + values = valuesMap.computeIfAbsent(startTimestampMillis, k -> + CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); + values.addProperty("temperature_" + channel, ((double) (startPoint + sampleOffset)) / 10f); + startTimestampMillis = startTimestampMillis + measurementPeriodMillis; + break; + case HUMIDITY: + values = valuesMap.computeIfAbsent(startTimestampMillis, k -> + CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); + values.addProperty("humidity_" + channel, (double) (startPoint + sampleOffset)); + startTimestampMillis = startTimestampMillis + measurementPeriodMillis; + break; + case ATMOSPHERIC_PRESSURE: + values = valuesMap.computeIfAbsent(startTimestampMillis, k -> + CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); + values.addProperty("pressure_" + channel, (double) (startPoint + sampleOffset) / 10f); + startTimestampMillis = startTimestampMillis + measurementPeriodMillis; + break; + case DIFFERENTIAL_PRESSURE: + values = valuesMap.computeIfAbsent(startTimestampMillis, k -> + CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); + values.addProperty("pressure_diff_" + channel, (double) (startPoint + sampleOffset)); + startTimestampMillis = startTimestampMillis + measurementPeriodMillis; + break; + case OK_ALARM: + boolean currentIsOk = sampleOffset < 0; + if (previousSampleOffset != null) { + boolean previousIsOk = previousSampleOffset < 0; + boolean isOk = previousIsOk && currentIsOk; + boolean isAlarm = !previousIsOk && !currentIsOk; + if (isOk || isAlarm) { + break; + } + } + String data = currentIsOk ? "OK" : "ALARM"; + long sampleOffsetMillis = TimeUnit.SECONDS.toMillis(sampleOffset); + long measurementTimestamp = startTimestampMillis + Math.abs(sampleOffsetMillis); + values = valuesMap.computeIfAbsent(measurementTimestamp - 1000, k -> + CoapEfentoUtils.setDefaultMeasurements(serialNumber, batteryStatus, measurementPeriod, nextTransmissionAtMillis, signal, k)); + values.addProperty("ok_alarm_" + channel, data); + break; + case NO_SENSOR: + case UNRECOGNIZED: + log.trace("[{}][{}] Sensor error value! Ignoring.", sessionId, measurementTypeName); + break; + default: + log.trace("[{}],[{}] Unsupported measurementType! Ignoring.", sessionId, measurementTypeName); + break; + } + } + } + } else { + log.trace("[{}][{}] sampleOffsetsList list is empty!", sessionId, measurementTypeName); + } + } + } else { + throw new IllegalStateException("[" + sessionId + "]: Failed to get Efento measurements, reason: channels list is empty!"); + } + if (!CollectionUtils.isEmpty(valuesMap)) { + List efentoMeasurements = new ArrayList<>(); + for (Long ts : valuesMap.keySet()) { + EfentoMeasurements measurement = new EfentoMeasurements(ts, valuesMap.get(ts)); + efentoMeasurements.add(measurement); + } + return efentoMeasurements; + } else { + throw new IllegalStateException("[" + sessionId + "]: Failed to collect Efento measurements, reason, values map is empty!"); + } + } + + @Data + @AllArgsConstructor + public static class EfentoMeasurements { + + private long ts; + private JsonObject values; + + } +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java new file mode 100644 index 0000000000..fdb2fa5443 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.efento.adaptor; + +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; + +import java.util.List; +import java.util.UUID; + +@Component +@Slf4j +public class EfentoCoapAdaptor { + + private static final Gson gson = new Gson(); + + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, List measurements) throws AdaptorException { + try { + return JsonConverter.convertToTelemetryProto(gson.toJsonTree(measurements)); + } catch (Exception ex) { + log.warn("[{}] Failed to convert EfentoMeasurements to PostTelemetry request!", sessionId); + throw new AdaptorException(ex); + } + } +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java new file mode 100644 index 0000000000..7eb08efc93 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/utils/CoapEfentoUtils.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.efento.utils; + +import com.google.gson.JsonObject; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class CoapEfentoUtils { + + public static String convertByteArrayToString(byte[] a) { + StringBuilder out = new StringBuilder(); + for (byte b : a) { + out.append(String.format("%02X", b)); + } + return out.toString(); + } + + public static String convertTimestampToUtcString(long timestampInMillis) { + String dateFormat = "yyyy-MM-dd HH:mm:ss"; + String utcZone = "UTC"; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone(utcZone)); + return String.format("%s UTC", simpleDateFormat.format(new Date(timestampInMillis))); + } + + public static JsonObject setDefaultMeasurements(String serialNumber, boolean batteryStatus, long measurementPeriod, long nextTransmissionAtMillis, long signal, long startTimestampMillis) { + JsonObject values = new JsonObject(); + values.addProperty("serial", serialNumber); + values.addProperty("battery", batteryStatus ? "ok" : "low"); + values.addProperty("measured_at", convertTimestampToUtcString(startTimestampMillis)); + values.addProperty("next_transmission_at", convertTimestampToUtcString(nextTransmissionAtMillis)); + values.addProperty("signal", signal); + values.addProperty("measurement_interval", measurementPeriod); + return values; + } + +} diff --git a/common/transport/coap/src/main/proto/proto_measurement_types.proto b/common/transport/coap/src/main/proto/proto_measurement_types.proto new file mode 100644 index 0000000000..eec5aa0586 --- /dev/null +++ b/common/transport/coap/src/main/proto/proto_measurement_types.proto @@ -0,0 +1,97 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +syntax = "proto3"; + +option java_package = "org.thingsboard.server.gen.transport.coap"; +option java_outer_classname = "MeasurementTypeProtos"; + +enum MeasurementType { + NO_SENSOR = 0; + + /* [°C] - Celsius degree. Resolution 0.1°C. Range [-273.2-4000.0]. Type: Continuous */ + TEMPERATURE = 1; + + /* [% RH] - Relative humidity. Resolution 1%. Range [0-100]. Type: Continuous */ + HUMIDITY = 2; + + /* [hPa] - Hectopascal (1hPa = 100Pa). Resolution 0.1hPa. Range: [1.0-2000.0]. Type: Continuous */ + ATMOSPHERIC_PRESSURE = 3; + + /* [Pa] - Pascal. Resolution 1Pa. Range [-10000-10000]Type: Continuous */ + DIFFERENTIAL_PRESSURE = 4; + + /* Sign indicates state: (+) ALARM, (-) OK. Type: Binary */ + OK_ALARM = 5; + + /* [IAQ] - Iaq index. Resolution 1IAQ. Range [0-500]. Sensor return also calibration status */ + /* as offset to measured value: */ + /* - offset 3000: Sensor not stabilized (always returns 25 IAQ value) */ + /* - offset 2000: Calibration required (sensor returns not accurate values) */ + /* - offset 1000: Calibration on-going (sensor returns not accurate values) */ + /* - offset 0: Calibration done (best accuracy of IAQ sensor) */ + /* Type: Continuous */ + IAQ = 6; + + /* Sign indicates water presence: (+) water not detected, (-) water detected. Type: Binary */ + FLOODING = 7; + + /* [NB] Number of pulses. Resolution 1 pulse. Range [0-16711679]. Type: Continuous */ + PULSE_CNT = 8; + + /* [Wh] - Watthour; Resolution 1Wh. Range [0-16711679]. Number of Watthours in a single period. Type: Continuous */ + ELECTRICITY_METER = 9; + + /* [l] - Liter. Resolution 1l. Range [0-16711679]. Number of litres in a single period. Type: Continuous */ + WATER_METER = 10; + + /* [kPa] - Kilopascal (1kPa = 1000Pa); Resolution 1kPa. Range [-1000-0]. Soil moisture (tension). Type: Continuous */ + SOIL_MOISTURE = 11; + + /* [ppm] - Parts per million. Resolution 1ppm. Range [0-1000000]. Carbon monoxide concentration. Type: Continuous */ + CO_GAS = 12; + + /* [ppm] - Parts per million. Resolution 0.01ppm. Range [0-1000000.00]. Nitrogen dioxide concentration. Type: Continuous*/ + NO2_GAS = 13; + + /* [ppm] - Parts per million. Resolution 1ppm. Range [0-1000000]. Hydrogen sulfide concentration. Type: Continuous */ + H2S_GAS = 14; + + /* [lx] - Illuminance. Resolution 0.1lx. Range [0-100000.0]. Type: Continuous */ + AMBIENT_LIGHT = 15; + + /* [µg/m^3] - Micro gram per cubic meter. Resolution 1µg/m^3 Range [0-1000]. */ + /* particles with an aerodynamic diameter less than 1 micrometer. Type: Continuous */ + PM_1_0 = 16; // µg/m^3 + + /* [µg/m^3] - Micro gram per cubic meter. Resolution 1µg/m^3 Range [0-1000]. */ + /* particles with an aerodynamic diameter less than 2.5 micrometers. Type: Continuous */ + PM_2_5 = 17; // µg/m^3 + + /* [µg/m^3] - Micro gram per cubic meter. Resolution 1µg/m^3 Range [0-1000]. */ + /* particles with an aerodynamic diameter less than 10 micrometers. Type: Continuous */ + PM_10_0 = 18; // µg/m^3 + + /* [dB] - Decibels. Resolution 0.1 dB. Range: [0-130.0]. Noise level. Type: Continuous */ + NOISE_LEVEL = 19; // 0.1 dB + + /* [ppm] - Parts per million. Resolution 1ppm. Range [0-1000000]. Ammonia concentration. Type: Continuous */ + NH3_GAS = 20; + + /* [ppm] - Parts per million. Resolution 1ppm. Range [0-1000000]. Methane concentration. Type: Continuous */ + CH4_GAS = 21; +} + + diff --git a/common/transport/coap/src/main/proto/proto_measurements.proto b/common/transport/coap/src/main/proto/proto_measurements.proto new file mode 100644 index 0000000000..8d894cdf3f --- /dev/null +++ b/common/transport/coap/src/main/proto/proto_measurements.proto @@ -0,0 +1,121 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +syntax = "proto3"; +import "proto_measurement_types.proto"; + +option java_package = "org.thingsboard.server.gen.transport.coap"; +option java_outer_classname = "MeasurementsProtos"; + +message ProtoChannel{ + /* Type of channel */ + MeasurementType type = 1; + + /* Timestamp of the first sample (the oldest one) in seconds since UNIX EPOCH 01-01-1970 */ + int32 timestamp = 2; + + /* Only used for 'Continuous' sensor types. Value used as the starting point for calculating the values of all */ + /* measurements in the package. */ + /* Format defined by 'MeasurementType' field */ + sint32 start_point = 4; + + /* 'Continuous' sensor types */ + /* Value of the offset from the 'start_point' for each measurement in the package. The oldest sample first ([0]). */ + /* 'sample_offsets' format defined by 'MeasurementType' field. */ + /* Example: MeasurementType = 1 (temperature), start_point = 100, sample_offsets[0] = 15, sample_offsets[1] = 20 */ + /* 1st sample in the package temperature value = 11.5 °C, 2nd sample in the package temperature value = 12 °C */ + /* Calculating timestamps of the measurements: timestamp = 1606391700, measurement_period_base = 60, */ + /* measurement_period_factor = 1. Timestamp of the 1st sample = 1606391700, timestamp of the 2nd sample = 1606391760 */ + /* 'Binary' sensor types: */ + /* Absolute value of the 'sample_offsets' field indicates the offset in seconds from 'timestamp' field. */ + /* Sign (- or +) indicates the state of measurements depend of sensor type. */ + /* Value of this field equals to '1' or '-1' indicates the state at the 'timestamp'. Other values */ + /* indicate the state of the relay at the time (in seconds) equal to 'timestamp' + value. */ + /* Values of this field are incremented starting from 1 (1->0: state at the time */ + /* of 'timestamp', 2->1: state at the time equal to 'timestamp' + 1 s, 3->2 : */ + /* state at the time equal to 'timestamp' + 2 s, etc.). The first and the last sample define the time range of the */ + /* measurements. Only state changes in the time range are included in the 'sample_offsets' field */ + /* Examples: if 'timestamp' value is 1553518060 and 'sample_offsets' equals '1', it means that at 1553518060 the state */ + /* was high, if 'timestamp' value is 1553518060 and 'sample_offsets' equals '-9', it means at 1553518068 the state was low */ + repeated sint32 sample_offsets = 5 [packed=true]; + + /* Deprecated - configuration is sent to endpoint 'c' */ + //int32 lo_threshold = 6; + + /* Deprecated - configuration is sent to endpoint 'c' */ + //int32 hi_threshold = 7; + + /* Deprecated - configurationis sent to endpoint 'c' */ + //int32 diff_threshold = 8; + } + +message ProtoMeasurements { + + /* serial number of the device */ + bytes serial_num = 1; + + /* true - battery ok, false - battery low */ + bool battery_status = 2; + + /* 'Measurement_period_base' and 'measurement_period_factor' define how often the measurements are taken. */ + /* Sensors of 'Continuous' type take measurement each Measurement_period_base * measurement_period_factor. */ + /* Sensors of 'Binary' type take measurement each Measurement_period_base. */ + /* For backward compatibility with versions 5.x in case of binary/mixed sensors, if the 'measurement_period_factor' is */ + /* not sent (equal to 0), then the default value '14' shall be used for period calculation. */ + /* For backward compatibility with versions 5.x in case of continues sensors, if the measurement_period_factor is */ + /* not sent (equal to 0), then the default value '1' shall be used for period calculation. */ + /* measurement period base in seconds */ + uint32 measurement_period_base = 3; + + /* Measurement period factor */ + uint32 measurement_period_factor = 8; + + repeated ProtoChannel channels = 4; + + /* Timestamp of the next scheduled transmission. If the device will not send data until this time, */ + /* it should be considered as 'lost' */ + uint32 next_transmission_at = 5; + + /* reason of transmission - unsigned integer where each bit indicates different */ + /* possible communication reason. Can be more than one */ + /* - bit 0: first message after sensor reset */ + /* - bit 1: user button triggered */ + /* - bit 2: user BLE triggered */ + /* - bit 3-7: number of retries -> incremented after each unsuccessful transmission. Max value 4. */ + /* Set to 0 after a successful transmission. */ + /* - bit 8: channel 1 lower threshold exceeded */ + /* - bit 9: channel 1 lower threshold returned */ + /* - bit 10: channel 1 higher threshold exceeded */ + /* - bit 11: channel 1 higher threshold returned */ + /* - bit 12: channel 1 differential threshold crossed */ + /* - bits 13-17: channel 2 thresholds (same as for channel 1) */ + /* - bits 18-22: channel 3 thresholds (same as for channel 1) */ + /* - bits 23-27: channel 4 or 5 or 6 thresholds (same as for channel 1) */ + uint32 transfer_reason = 6; + + /* Signal strength level mapped from RSSI */ + /* - 0 : 113 dBm or less */ + /* - 1 : 111 dBm */ + /* - 2...30 : 109...-53 dBm */ + /* - 31 : -51 dBm or greater */ + /* - 99 : Not known or not detectable */ + uint32 signal = 7; + + /* Hash of the current configuration. Hash value changes each time a device receives a new configuration */ + uint32 hash = 9; + + /* Optional string up to 36 bytes long. Can be set to any user define value or hold device's IMEI */ + string cloud_token = 16; +} \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index ea20f6fb50..d8c69bc7fa 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -22,6 +22,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.MalformedJsonException; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; @@ -111,7 +112,7 @@ public class JsonConverter { public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) { long durationMs = 0L; if (json != null && !json.isEmpty()) { - return convertToClaimDeviceProto(deviceId, new JsonParser().parse(json)); + return convertToClaimDeviceProto(deviceId, JSON_PARSER.parse(json)); } return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs); } @@ -160,7 +161,15 @@ public class JsonConverter { result.addProperty("id", msg.getRequestId()); } result.addProperty("method", msg.getMethodName()); - result.add("params", new JsonParser().parse(msg.getParams())); + try { + result.add("params", JSON_PARSER.parse(msg.getParams())); + } catch (JsonSyntaxException ex) { + if (ex.getCause() instanceof MalformedJsonException) { + result.addProperty("params", msg.getParams()); + } else { + throw ex; + } + } return result; } @@ -409,7 +418,7 @@ public class JsonConverter { public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) { if (StringUtils.isEmpty(msg.getError())) { - return new JsonParser().parse(msg.getPayload()); + return JSON_PARSER.parse(msg.getPayload()); } else { JsonObject errorMsg = new JsonObject(); errorMsg.addProperty("error", msg.getError()); @@ -567,7 +576,7 @@ public class JsonConverter { } public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { - JsonElement jsonElement = new JsonParser().parse(json); + JsonElement jsonElement = JSON_PARSER.parse(json); if (jsonElement.isJsonObject()) { return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); } else { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index 458d29766e..fc82f78633 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -73,6 +73,9 @@ public class ProtoConverter { } public static TransportProtos.ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, byte[] bytes) throws InvalidProtocolBufferException { + if (bytes == null) { + return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, 0); + } TransportApiProtos.ClaimDevice proto = TransportApiProtos.ClaimDevice.parseFrom(bytes); String secretKey = proto.getSecretKey() != null ? proto.getSecretKey() : DataConstants.DEFAULT_SECRET_KEY; long durationMs = proto.getDurationMs(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 95f52cf55e..bff523d956 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -41,6 +41,9 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; @@ -49,6 +52,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -355,11 +359,17 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); - try { - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); + validateProtoSchemas(protoTransportPayloadTypeConfiguration); + } + } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + validateProtoSchemas(protoTransportPayloadConfiguration); } } } @@ -403,6 +413,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } + private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } + } + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); ProtoFileElement protoFileElement; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index c5e27bf886..d8f2444cb5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -266,6 +267,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe case LWM2M: deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); break; + case COAP: + deviceData.setTransportConfiguration(new CoapDeviceTransportConfiguration()); + break; } } return deviceData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 02911e7f0e..5276b20832 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -304,6 +304,7 @@ public class ModelConstants { public static final String WIDGETS_BUNDLE_ALIAS_PROPERTY = ALIAS_PROPERTY; public static final String WIDGETS_BUNDLE_TITLE_PROPERTY = TITLE_PROPERTY; public static final String WIDGETS_BUNDLE_IMAGE_PROPERTY = "image"; + public static final String WIDGETS_BUNDLE_DESCRIPTION = "description"; public static final String WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_search_text"; public static final String WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_alias"; @@ -316,6 +317,8 @@ public class ModelConstants { public static final String WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY = "bundle_alias"; public static final String WIDGET_TYPE_ALIAS_PROPERTY = ALIAS_PROPERTY; public static final String WIDGET_TYPE_NAME_PROPERTY = "name"; + public static final String WIDGET_TYPE_IMAGE_PROPERTY = "image"; + public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description"; public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor"; public static final String WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME = "widget_type_by_tenant_and_aliases"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java new file mode 100644 index 0000000000..803d1604f8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@MappedSuperclass +public abstract class AbstractWidgetTypeEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY) + private UUID tenantId; + + @Column(name = ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY) + private String bundleAlias; + + @Column(name = ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY) + private String alias; + + @Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY) + private String name; + + public AbstractWidgetTypeEntity() { + super(); + } + + public AbstractWidgetTypeEntity(BaseWidgetType widgetType) { + if (widgetType.getId() != null) { + this.setUuid(widgetType.getId().getId()); + } + this.setCreatedTime(widgetType.getCreatedTime()); + if (widgetType.getTenantId() != null) { + this.tenantId = widgetType.getTenantId().getId(); + } + this.bundleAlias = widgetType.getBundleAlias(); + this.alias = widgetType.getAlias(); + this.name = widgetType.getName(); + } + + public AbstractWidgetTypeEntity(AbstractWidgetTypeEntity widgetTypeEntity) { + this.setId(widgetTypeEntity.getId()); + this.setCreatedTime(widgetTypeEntity.getCreatedTime()); + this.tenantId = widgetTypeEntity.getTenantId(); + this.bundleAlias = widgetTypeEntity.getBundleAlias(); + this.alias = widgetTypeEntity.getAlias(); + this.name = widgetTypeEntity.getName(); + } + + protected BaseWidgetType toBaseWidgetType() { + BaseWidgetType widgetType = new BaseWidgetType(new WidgetTypeId(getUuid())); + widgetType.setCreatedTime(createdTime); + if (tenantId != null) { + widgetType.setTenantId(new TenantId(tenantId)); + } + widgetType.setBundleAlias(bundleAlias); + widgetType.setAlias(alias); + widgetType.setName(name); + return widgetType; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java new file mode 100644 index 0000000000..220ed2852e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.WIDGET_TYPE_COLUMN_FAMILY_NAME) +public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity { + + @Column(name = ModelConstants.WIDGET_TYPE_IMAGE_PROPERTY) + private String image; + + @Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTION_PROPERTY) + private String description; + + @Type(type="json") + @Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY) + private JsonNode descriptor; + + public WidgetTypeDetailsEntity() { + super(); + } + + public WidgetTypeDetailsEntity(WidgetTypeDetails widgetTypeDetails) { + super(widgetTypeDetails); + this.image = widgetTypeDetails.getImage(); + this.description = widgetTypeDetails.getDescription(); + this.descriptor = widgetTypeDetails.getDescriptor(); + } + + @Override + public WidgetTypeDetails toData() { + BaseWidgetType baseWidgetType = super.toBaseWidgetType(); + WidgetTypeDetails widgetTypeDetails = new WidgetTypeDetails(baseWidgetType); + widgetTypeDetails.setImage(image); + widgetTypeDetails.setDescription(description); + widgetTypeDetails.setDescriptor(descriptor); + return widgetTypeDetails; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java index 39e01630e6..558e404899 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java @@ -20,11 +20,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.WidgetType; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -38,19 +35,7 @@ import java.util.UUID; @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.WIDGET_TYPE_COLUMN_FAMILY_NAME) -public final class WidgetTypeEntity extends BaseSqlEntity implements BaseEntity { - - @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY) - private String bundleAlias; - - @Column(name = ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY) - private String alias; - - @Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY) - private String name; +public final class WidgetTypeEntity extends AbstractWidgetTypeEntity { @Type(type="json") @Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY) @@ -60,30 +45,10 @@ public final class WidgetTypeEntity extends BaseSqlEntity implement super(); } - public WidgetTypeEntity(WidgetType widgetType) { - if (widgetType.getId() != null) { - this.setUuid(widgetType.getId().getId()); - } - this.setCreatedTime(widgetType.getCreatedTime()); - if (widgetType.getTenantId() != null) { - this.tenantId = widgetType.getTenantId().getId(); - } - this.bundleAlias = widgetType.getBundleAlias(); - this.alias = widgetType.getAlias(); - this.name = widgetType.getName(); - this.descriptor = widgetType.getDescriptor(); - } - @Override public WidgetType toData() { - WidgetType widgetType = new WidgetType(new WidgetTypeId(this.getUuid())); - widgetType.setCreatedTime(createdTime); - if (tenantId != null) { - widgetType.setTenantId(new TenantId(tenantId)); - } - widgetType.setBundleAlias(bundleAlias); - widgetType.setAlias(alias); - widgetType.setName(name); + BaseWidgetType baseWidgetType = super.toBaseWidgetType(); + WidgetType widgetType = new WidgetType(baseWidgetType); widgetType.setDescriptor(descriptor); return widgetType; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java new file mode 100644 index 0000000000..75600ac0a9 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; + +@Data +@EqualsAndHashCode(callSuper = true) +public final class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity { + + private String image; + private String description; + private String widgetType; + + public WidgetTypeInfoEntity() { + super(); + } + + public WidgetTypeInfoEntity(WidgetTypeDetailsEntity widgetTypeDetailsEntity) { + super(widgetTypeDetailsEntity); + this.image = widgetTypeDetailsEntity.getImage(); + this.description = widgetTypeDetailsEntity.getDescription(); + if (widgetTypeDetailsEntity.getDescriptor() != null && widgetTypeDetailsEntity.getDescriptor().has("type")) { + this.widgetType = widgetTypeDetailsEntity.getDescriptor().get("type").asText(); + } else { + this.widgetType = ""; + } + } + + @Override + public WidgetTypeInfo toData() { + BaseWidgetType baseWidgetType = super.toBaseWidgetType(); + WidgetTypeInfo widgetTypeInfo = new WidgetTypeInfo(baseWidgetType); + widgetTypeInfo.setImage(image); + widgetTypeInfo.setDescription(description); + widgetTypeInfo.setWidgetType(widgetType); + return widgetTypeInfo; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java index 087369e933..205ebf23f9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java @@ -48,6 +48,12 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; + @Column(name = ModelConstants.WIDGETS_BUNDLE_IMAGE_PROPERTY) + private String image; + + @Column(name = ModelConstants.WIDGETS_BUNDLE_DESCRIPTION) + private String description; + public WidgetsBundleEntity() { super(); } @@ -62,6 +68,8 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl } this.alias = widgetsBundle.getAlias(); this.title = widgetsBundle.getTitle(); + this.image = widgetsBundle.getImage(); + this.description = widgetsBundle.getDescription(); } @Override @@ -83,6 +91,8 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl } widgetsBundle.setAlias(alias); widgetsBundle.setTitle(title); + widgetsBundle.setImage(image); + widgetsBundle.setDescription(description); return widgetsBundle; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index f34048c5ad..b422f1cd4a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -76,12 +76,17 @@ public interface AlarmRepository extends CrudRepository { @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT alarm.severity FROM AlarmEntity alarm" + - " WHERE alarm.tenantId = :tenantId" + - " AND alarm.originatorId = :entityId" + - " AND ((:status) IS NULL OR alarm.status in (:status))") + @Query(value = "SELECT a.severity FROM AlarmEntity a " + + "LEFT JOIN RelationEntity re ON a.id = re.toId " + + "AND re.relationTypeGroup = 'ALARM' " + + "AND re.toType = 'ALARM' " + + "AND re.fromId = :affectedEntityId " + + "AND re.fromType = :affectedEntityType " + + "WHERE a.tenantId = :tenantId " + + "AND (a.originatorId = :affectedEntityId or re.fromId IS NOT NULL) " + + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))") Set findAlarmSeverities(@Param("tenantId") UUID tenantId, - @Param("entityId") UUID entityId, - @Param("status") Set status); - + @Param("affectedEntityId") UUID affectedEntityId, + @Param("affectedEntityType") String affectedEntityType, + @Param("alarmStatuses") Set alarmStatuses); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 722928e1b8..bfca088c69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -123,7 +123,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status) { - return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), status); + public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses) { + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 2888da504c..a3544eb513 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -18,8 +18,12 @@ package org.thingsboard.server.dao.sql.widget; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.WidgetTypeDetailsEntity; import org.thingsboard.server.dao.model.sql.WidgetTypeEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.widget.WidgetTypeDao; @@ -31,28 +35,43 @@ import java.util.UUID; * Created by Valerii Sosliuk on 4/29/2017. */ @Component -public class JpaWidgetTypeDao extends JpaAbstractDao implements WidgetTypeDao { +public class JpaWidgetTypeDao extends JpaAbstractDao implements WidgetTypeDao { @Autowired private WidgetTypeRepository widgetTypeRepository; @Override - protected Class getEntityClass() { - return WidgetTypeEntity.class; + protected Class getEntityClass() { + return WidgetTypeDetailsEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected CrudRepository getCrudRepository() { return widgetTypeRepository; } + @Override + public WidgetType findWidgetTypeById(TenantId tenantId, UUID widgetTypeId) { + return DaoUtil.getData(widgetTypeRepository.findWidgetTypeById(widgetTypeId)); + } + @Override public List findWidgetTypesByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias) { + return DaoUtil.convertDataList(widgetTypeRepository.findWidgetTypesByTenantIdAndBundleAlias(tenantId, bundleAlias)); + } + + @Override + public List findWidgetTypesDetailsByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias) { return DaoUtil.convertDataList(widgetTypeRepository.findByTenantIdAndBundleAlias(tenantId, bundleAlias)); } + @Override + public List findWidgetTypesInfosByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias) { + return DaoUtil.convertDataList(widgetTypeRepository.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId, bundleAlias)); + } + @Override public WidgetType findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias) { - return DaoUtil.getData(widgetTypeRepository.findByTenantIdAndBundleAliasAndAlias(tenantId, bundleAlias, alias)); + return DaoUtil.getData(widgetTypeRepository.findWidgetTypeByTenantIdAndBundleAliasAndAlias(tenantId, bundleAlias, alias)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java index 7269128ca1..ad21e2e40a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java @@ -15,18 +15,35 @@ */ package org.thingsboard.server.dao.sql.widget; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.model.sql.WidgetTypeDetailsEntity; import org.thingsboard.server.dao.model.sql.WidgetTypeEntity; +import org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity; import java.util.List; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 4/29/2017. - */ -public interface WidgetTypeRepository extends CrudRepository { +public interface WidgetTypeRepository extends CrudRepository { + + @Query("SELECT wt FROM WidgetTypeEntity wt WHERE wt.id = :widgetTypeId") + WidgetTypeEntity findWidgetTypeById(@Param("widgetTypeId") UUID widgetTypeId); + + @Query("SELECT wt FROM WidgetTypeEntity wt WHERE wt.tenantId = :tenantId AND wt.bundleAlias = :bundleAlias") + List findWidgetTypesByTenantIdAndBundleAlias(@Param("tenantId") UUID tenantId, + @Param("bundleAlias") String bundleAlias); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity(wtd) FROM WidgetTypeDetailsEntity wtd " + + "WHERE wtd.tenantId = :tenantId AND wtd.bundleAlias = :bundleAlias") + List findWidgetTypesInfosByTenantIdAndBundleAlias(@Param("tenantId") UUID tenantId, + @Param("bundleAlias") String bundleAlias); - List findByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); + List findByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); - WidgetTypeEntity findByTenantIdAndBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias); + @Query("SELECT wt FROM WidgetTypeEntity wt " + + "WHERE wt.tenantId = :tenantId AND wt.bundleAlias = :bundleAlias AND wt.alias = :alias") + WidgetTypeEntity findWidgetTypeByTenantIdAndBundleAliasAndAlias(@Param("tenantId") UUID tenantId, + @Param("bundleAlias") String bundleAlias, + @Param("alias") String alias); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java index 43e972339d..3937b7c2c5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java @@ -123,7 +123,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme Map trueLatest = new HashMap<>(); v.forEach(ts -> { TsKey key = new TsKey(ts.getEntityId(), ts.getKey()); - trueLatest.merge(key, ts, (oldTs, newTs) -> oldTs.getTs() < newTs.getTs() ? newTs : oldTs); + trueLatest.merge(key, ts, (oldTs, newTs) -> oldTs.getTs() <= newTs.getTs() ? newTs : oldTs); }); List latestEntities = new ArrayList<>(trueLatest.values()); if (batchSortEnabled) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index b36af1b0e6..981f859ca1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -33,7 +33,7 @@ public interface TsKvTimescaleRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "AND tskv.ts >= :startTs AND tskv.ts < :endTs") List findAllWithLimit( @Param("entityId") UUID entityId, @Param("entityKey") int key, @@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "AND tskv.ts >= :startTs AND tskv.ts < :endTs") void delete(@Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java index 8a59bc9c29..db5534d73e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java @@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture; public interface TsKvRepository extends CrudRepository { @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") List findAllWithLimit(@Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, @@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") void delete(@Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, @@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findStringMax(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findNumericMax(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findStringMin(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findNumericMin( @Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findCount(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findAvg(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") CompletableFuture findSum(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 2f817634c3..7d09578dd1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM - + "AND " + ModelConstants.TS_COLUMN + " > ? " - + "AND " + ModelConstants.TS_COLUMN + " <= ?"); + + "AND " + ModelConstants.TS_COLUMN + " >= ? " + + "AND " + ModelConstants.TS_COLUMN + " < ?"); } return deleteStmt; } @@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM - + "AND " + ModelConstants.TS_COLUMN + " > ? " - + "AND " + ModelConstants.TS_COLUMN + " <= ?" + + "AND " + ModelConstants.TS_COLUMN + " >= ? " + + "AND " + ModelConstants.TS_COLUMN + " < ?" + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : "")); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java index 88600ea8e3..261c184800 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.widget; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -25,15 +27,24 @@ import java.util.UUID; /** * The Interface WidgetTypeDao. */ -public interface WidgetTypeDao extends Dao { +public interface WidgetTypeDao extends Dao { /** * Save or update widget type object * - * @param widgetType the widget type object + * @param widgetTypeDetails the widget type details object * @return saved widget type object */ - WidgetType save(TenantId tenantId, WidgetType widgetType); + WidgetTypeDetails save(TenantId tenantId, WidgetTypeDetails widgetTypeDetails); + + /** + * Find widget type by tenantId and widgetTypeId. + * + * @param tenantId the tenantId + * @param widgetTypeId the widget type id + * @return the widget type object + */ + WidgetType findWidgetTypeById(TenantId tenantId, UUID widgetTypeId); /** * Find widget types by tenantId and bundleAlias. @@ -44,6 +55,24 @@ public interface WidgetTypeDao extends Dao { */ List findWidgetTypesByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); + /** + * Find widget types details by tenantId and bundleAlias. + * + * @param tenantId the tenantId + * @param bundleAlias the bundle alias + * @return the list of widget types details objects + */ + List findWidgetTypesDetailsByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); + + /** + * Find widget types infos by tenantId and bundleAlias. + * + * @param tenantId the tenantId + * @param bundleAlias the bundle alias + * @return the list of widget types infos objects + */ + List findWidgetTypesInfosByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); + /** * Find widget type by tenantId, bundleAlias and alias. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index 85bb876d50..d8a8ea3e35 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java @@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -51,14 +53,21 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) { log.trace("Executing findWidgetTypeById [{}]", widgetTypeId); Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); + return widgetTypeDao.findWidgetTypeById(tenantId, widgetTypeId.getId()); + } + + @Override + public WidgetTypeDetails findWidgetTypeDetailsById(TenantId tenantId, WidgetTypeId widgetTypeId) { + log.trace("Executing findWidgetTypeDetailsById [{}]", widgetTypeId); + Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); return widgetTypeDao.findById(tenantId, widgetTypeId.getId()); } @Override - public WidgetType saveWidgetType(WidgetType widgetType) { - log.trace("Executing saveWidgetType [{}]", widgetType); - widgetTypeValidator.validate(widgetType, WidgetType::getTenantId); - return widgetTypeDao.save(widgetType.getTenantId(), widgetType); + public WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetTypeDetails) { + log.trace("Executing saveWidgetType [{}]", widgetTypeDetails); + widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId); + return widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); } @Override @@ -76,6 +85,22 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { return widgetTypeDao.findWidgetTypesByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias); } + @Override + public List findWidgetTypesDetailsByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) { + log.trace("Executing findWidgetTypesDetailsByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias); + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + Validator.validateString(bundleAlias, INCORRECT_BUNDLE_ALIAS + bundleAlias); + return widgetTypeDao.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias); + } + + @Override + public List findWidgetTypesInfosByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) { + log.trace("Executing findWidgetTypesInfosByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias); + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + Validator.validateString(bundleAlias, INCORRECT_BUNDLE_ALIAS + bundleAlias); + return widgetTypeDao.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias); + } + @Override public WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias) { log.trace("Executing findWidgetTypeByTenantIdBundleAliasAndAlias, tenantId [{}], bundleAlias [{}], alias [{}]", tenantId, bundleAlias, alias); @@ -96,24 +121,24 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { } } - private DataValidator widgetTypeValidator = - new DataValidator() { + private DataValidator widgetTypeValidator = + new DataValidator<>() { @Override - protected void validateDataImpl(TenantId tenantId, WidgetType widgetType) { - if (StringUtils.isEmpty(widgetType.getName())) { + protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + if (StringUtils.isEmpty(widgetTypeDetails.getName())) { throw new DataValidationException("Widgets type name should be specified!"); } - if (StringUtils.isEmpty(widgetType.getBundleAlias())) { + if (StringUtils.isEmpty(widgetTypeDetails.getBundleAlias())) { throw new DataValidationException("Widgets type bundle alias should be specified!"); } - if (widgetType.getDescriptor() == null || widgetType.getDescriptor().size() == 0) { + if (widgetTypeDetails.getDescriptor() == null || widgetTypeDetails.getDescriptor().size() == 0) { throw new DataValidationException("Widgets type descriptor can't be empty!"); } - if (widgetType.getTenantId() == null) { - widgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID)); + if (widgetTypeDetails.getTenantId() == null) { + widgetTypeDetails.setTenantId(new TenantId(ModelConstants.NULL_UUID)); } - if (!widgetType.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, widgetType.getTenantId().getId()); + if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId()); if (tenant == null) { throw new DataValidationException("Widget type is referencing to non-existent tenant!"); } @@ -121,37 +146,37 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { } @Override - protected void validateCreate(TenantId tenantId, WidgetType widgetType) { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetType.getTenantId().getId(), widgetType.getBundleAlias()); + protected void validateCreate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias()); if (widgetsBundle == null) { throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!"); } - String alias = widgetType.getAlias(); + String alias = widgetTypeDetails.getAlias(); if (alias == null || alias.trim().isEmpty()) { - alias = widgetType.getName().toLowerCase().replaceAll("\\W+", "_"); + alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_"); } String originalAlias = alias; int c = 1; WidgetType withSameAlias; do { - withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetType.getTenantId().getId(), widgetType.getBundleAlias(), alias); + withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias); if (withSameAlias != null) { alias = originalAlias + (++c); } } while (withSameAlias != null); - widgetType.setAlias(alias); + widgetTypeDetails.setAlias(alias); } @Override - protected void validateUpdate(TenantId tenantId, WidgetType widgetType) { - WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetType.getId().getId()); - if (!storedWidgetType.getTenantId().getId().equals(widgetType.getTenantId().getId())) { + protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId()); + if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) { throw new DataValidationException("Can't move existing widget type to different tenant!"); } - if (!storedWidgetType.getBundleAlias().equals(widgetType.getBundleAlias())) { + if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) { throw new DataValidationException("Update of widget type bundle alias is prohibited!"); } - if (!storedWidgetType.getAlias().equals(widgetType.getAlias())) { + if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) { throw new DataValidationException("Update of widget type alias is prohibited!"); } } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 6dd6b3ad7d..96d2a8ff13 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -291,7 +291,9 @@ CREATE TABLE IF NOT EXISTS widget_type ( bundle_alias varchar(255), descriptor varchar(1000000), name varchar(255), - tenant_id uuid + tenant_id uuid, + image varchar(1000000), + description varchar(255) ); CREATE TABLE IF NOT EXISTS widgets_bundle ( @@ -300,7 +302,9 @@ CREATE TABLE IF NOT EXISTS widgets_bundle ( alias varchar(255), search_text varchar(255), tenant_id uuid, - title varchar(255) + title varchar(255), + image varchar(1000000), + description varchar(255) ); CREATE TABLE IF NOT EXISTS entity_view ( diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 3ae21dc8a3..91ec86a8c4 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -43,3 +43,5 @@ CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, cu CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc); + +CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 41c7c3d9db..3d37f63f92 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -316,7 +316,9 @@ CREATE TABLE IF NOT EXISTS widget_type ( bundle_alias varchar(255), descriptor varchar(1000000), name varchar(255), - tenant_id uuid + tenant_id uuid, + image varchar(1000000), + description varchar(255) ); CREATE TABLE IF NOT EXISTS widgets_bundle ( @@ -325,7 +327,9 @@ CREATE TABLE IF NOT EXISTS widgets_bundle ( alias varchar(255), search_text varchar(255), tenant_id uuid, - title varchar(255) + title varchar(255), + image varchar(1000000), + description varchar(255) ); CREATE TABLE IF NOT EXISTS entity_view ( diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index b5808133ec..d0104c45c3 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -91,8 +91,8 @@ $$ DECLARE tenant_cursor CURSOR FOR select tenant.id as tenant_id from tenant; - tenant_id_record varchar; - customer_id_record varchar; + tenant_id_record uuid; + customer_id_record uuid; tenant_ttl bigint; customer_ttl bigint; deleted_for_entities bigint; diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 4f26e4eb4e..c17c46c8a8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,10 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.sql.*SqlTest" + "org.thingsboard.server.dao.service.sql.*SqlTest", + "org.thingsboard.server.dao.service.attributes.sql.*SqlTest", + "org.thingsboard.server.dao.service.event.sql.*SqlTest", + "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest" }) public class SqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetTypeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetTypeServiceTest.java index 3d3d52003f..ba500f950d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetTypeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetTypeServiceTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -62,12 +63,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); Assert.assertNotNull(savedWidgetType); Assert.assertNotNull(savedWidgetType.getId()); @@ -94,7 +95,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); @@ -107,7 +108,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveWidgetTypeWithEmptyBundleAlias() throws IOException { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); @@ -121,7 +122,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setName("Widget Type"); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); @@ -140,7 +141,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(new TenantId(Uuids.timeBased())); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); @@ -154,7 +155,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveWidgetTypeWithInvalidBundleAlias() throws IOException { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias("some_alias"); widgetType.setName("Widget Type"); @@ -169,12 +170,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); savedWidgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID)); try { widgetTypeService.saveWidgetType(savedWidgetType); @@ -190,12 +191,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); savedWidgetType.setBundleAlias("some_alias"); try { widgetTypeService.saveWidgetType(savedWidgetType); @@ -211,12 +212,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); savedWidgetType.setAlias("some_alias"); try { widgetTypeService.saveWidgetType(savedWidgetType); @@ -232,13 +233,13 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); - WidgetType foundWidgetType = widgetTypeService.findWidgetTypeById(tenantId, savedWidgetType.getId()); + WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetTypeDetails foundWidgetType = widgetTypeService.findWidgetTypeDetailsById(tenantId, savedWidgetType.getId()); Assert.assertNotNull(foundWidgetType); Assert.assertEquals(savedWidgetType, foundWidgetType); @@ -252,12 +253,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType); + WidgetType savedWidgetType = new WidgetType(widgetTypeService.saveWidgetType(widgetType)); WidgetType foundWidgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias()); Assert.assertNotNull(foundWidgetType); Assert.assertEquals(savedWidgetType, foundWidgetType); @@ -272,7 +273,7 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { widgetsBundle.setTitle("Widgets bundle"); WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type"); @@ -296,12 +297,12 @@ public abstract class BaseWidgetTypeServiceTest extends AbstractServiceTest { List widgetTypes = new ArrayList<>(); for (int i=0;i<121;i++) { - WidgetType widgetType = new WidgetType(); + WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setName("Widget Type " + i); widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); - widgetTypes.add(widgetTypeService.saveWidgetType(widgetType)); + widgetTypes.add(new WidgetType(widgetTypeService.saveWidgetType(widgetType))); } List loadedWidgetTypes = widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, savedWidgetsBundle.getAlias()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 8ef3e59fcc..b7872fc239 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -143,52 +143,52 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { public void testFindByQueryAscOrder() throws Exception { DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 3); saveEntries(deviceId, TS - 2); saveEntries(deviceId, TS - 1); - saveEntries(deviceId, TS); List queries = new ArrayList<>(); queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC")); List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(3, entries.size()); - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0)); - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1)); - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2)); + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0)); + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2)); EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(3, entries.size()); - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0)); - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1)); - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2)); + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0)); + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2)); } @Test public void testFindByQueryDescOrder() throws Exception { DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 3); saveEntries(deviceId, TS - 2); saveEntries(deviceId, TS - 1); - saveEntries(deviceId, TS); List queries = new ArrayList<>(); queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC")); List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(3, entries.size()); - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0)); - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1)); - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2)); + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0)); + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2)); EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(3, entries.size()); - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0)); - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1)); - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2)); + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0)); + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2)); } @Test diff --git a/k8s/common/tb-coap-transport-configmap.yml b/k8s/common/tb-coap-transport-configmap.yml index f38ee7ce0e..0f0ea0a8e5 100644 --- a/k8s/common/tb-coap-transport-configmap.yml +++ b/k8s/common/tb-coap-transport-configmap.yml @@ -23,11 +23,11 @@ metadata: name: tb-coap-transport-config data: conf: | - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-coap-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}-heapdump.bin" + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-coap-transport.out export LOADER_PATH=/usr/share/tb-coap-transport/conf logback: | diff --git a/k8s/common/tb-http-transport-configmap.yml b/k8s/common/tb-http-transport-configmap.yml index ffd4c6833c..582df2e5e8 100644 --- a/k8s/common/tb-http-transport-configmap.yml +++ b/k8s/common/tb-http-transport-configmap.yml @@ -23,11 +23,11 @@ metadata: name: tb-http-transport-config data: conf: | - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-http-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}-heapdump.bin" + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-http-transport.out export LOADER_PATH=/usr/share/tb-http-transport/conf logback: | diff --git a/k8s/common/tb-mqtt-transport-configmap.yml b/k8s/common/tb-mqtt-transport-configmap.yml index 14db242110..0d82938842 100644 --- a/k8s/common/tb-mqtt-transport-configmap.yml +++ b/k8s/common/tb-mqtt-transport-configmap.yml @@ -23,11 +23,11 @@ metadata: name: tb-mqtt-transport-config data: conf: | - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-heapdump.bin" + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-mqtt-transport.out export LOADER_PATH=/usr/share/tb-mqtt-transport/conf logback: | diff --git a/k8s/common/tb-node-configmap.yml b/k8s/common/tb-node-configmap.yml index fd47c9b785..3e796215ab 100644 --- a/k8s/common/tb-node-configmap.yml +++ b/k8s/common/tb-node-configmap.yml @@ -24,11 +24,11 @@ metadata: data: conf: | export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/thingsboard/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}-heapdump.bin" + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=thingsboard.out export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions logback: | diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 9af00d44c7..68331569ab 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -37,7 +37,7 @@ true 1.9.1 1.10 - 4.5.6 + 4.5.13 diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 85440a92a3..fd60364247 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -25,7 +25,7 @@ kafka: # Kafka Bootstrap Servers servers: "localhost:9092" replication_factor: "1" - topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100" + topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1" use_confluent_cloud: false confluent: sasl: diff --git a/pom.xml b/pom.xml index 7bbf0783ad..970d0d08c6 100755 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 1.3.2 2.3.2 2.3.2 - 2.3.5.RELEASE + 2.3.9.RELEASE 5.2.10.RELEASE 5.4.1 2.4.1 @@ -54,7 +54,8 @@ 4.10.0 4.0.5 4.3.1.0 - 3.11.9 + 3.11.10 + 3.11.0 1.2.7 28.2-jre 2.6.1 @@ -78,7 +79,7 @@ 1.22.1 1.18.18 1.2.4 - 4.1.53.Final + 4.1.60.Final 1.5.0 4.8.0 2.19.1 @@ -1150,6 +1151,11 @@ java-driver-query-builder ${cassandra.version} + + com.datastax.cassandra + cassandra-driver-core + ${cassandra-driver-core.version} + io.dropwizard.metrics metrics-jmx diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index ae6563c1df..d6a0a3e576 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -28,15 +27,12 @@ import org.thingsboard.server.common.data.id.AlarmId; 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.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.common.data.query.AlarmDataPageLink; import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.dao.alarm.AlarmOperationResult; import java.util.Collection; -import java.util.List; /** * Created by ashvayka on 02.04.18. @@ -51,6 +47,8 @@ public interface RuleEngineAlarmService { ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index ca5f148ae3..49420e8feb 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -25,7 +25,7 @@ import java.util.Set; public interface ScriptEngine { - TbMsg executeUpdate(TbMsg msg) throws ScriptException; + List executeUpdate(TbMsg msg) throws ScriptException; ListenableFuture> executeUpdateAsync(TbMsg msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java index 54be64b7fa..33f4210955 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java @@ -27,21 +27,28 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.util.encoders.Hex; import org.springframework.util.StringUtils; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.math.BigInteger; +import java.nio.ByteBuffer; import java.security.AlgorithmParameters; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyStore; +import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Security; import java.security.cert.Certificate; @@ -49,6 +56,9 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Data @Slf4j @@ -61,6 +71,15 @@ public class CertPemCredentials implements ClientCredentials { private String privateKey; private String password; + static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*" + + "-----BEGIN RSA PRIVATE KEY-----" + "\\s*" + + "Proc-Type: 4,ENCRYPTED" + "\\s*" + + "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+" + + "([\\s\\S]*)" + + "-----END RSA PRIVATE KEY-----" + "\\s*"; + + static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX); + @Override public CredentialsType getType() { return CredentialsType.CERT_PEM; @@ -103,7 +122,7 @@ public class CertPemCredentials implements ClientCredentials { KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject); privateKey = key.getPrivate(); } else if (keyObject instanceof PrivateKey) { - privateKey = (PrivateKey)keyObject; + privateKey = (PrivateKey) keyObject; } else { throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass()); } @@ -152,34 +171,144 @@ public class CertPemCredentials implements ClientCredentials { private PrivateKey readPrivateKeyFile(String fileContent) throws Exception { PrivateKey privateKey = null; if (fileContent != null && !fileContent.isEmpty()) { - fileContent = fileContent.replaceAll(".*BEGIN.*PRIVATE KEY.*", "") - .replaceAll(".*END.*PRIVATE KEY.*", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - KeySpec keySpec = getKeySpec(decoded); + KeySpec keySpec = getKeySpec(fileContent); privateKey = keyFactory.generatePrivate(keySpec); } return privateKey; } - private KeySpec getKeySpec(byte[] encodedKey) throws Exception { - KeySpec keySpec; - if (password == null || password.isEmpty()) { - keySpec = new PKCS8EncodedKeySpec(encodedKey); + private KeySpec getKeySpec(String encodedKey) throws Exception { + KeySpec keySpec = null; + Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(encodedKey); + if (matcher.matches()) { + String encryptionDetails = matcher.group(1).trim(); + String encryptedKey = matcher.group(2).replaceAll("\\s", ""); + byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey); + String[] encryptionDetailsParts = encryptionDetails.split(","); + if (encryptionDetailsParts.length == 2) { + String encryptionAlgorithm = encryptionDetailsParts[0]; + String encryptedAlgorithmParams = encryptionDetailsParts[1]; + byte[] pw = password.getBytes(); + byte[] iv = Hex.decode(encryptedAlgorithmParams); + + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(pw); + digest.update(iv, 0, 8); + + byte[] round1Digest = digest.digest(); + digest.update(round1Digest); + digest.update(pw); + digest.update(iv, 0, 8); + + byte[] round2Digest = digest.digest(); + Cipher cipher = null; + SecretKey secretKey = null; + byte[] key = null; + + switch(encryptionAlgorithm) { + case "AES-256-CBC": + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + key = new byte[32]; + System.arraycopy(round1Digest, 0, key, 0, 16); + System.arraycopy(round2Digest, 0, key, 16, 16); + secretKey = new SecretKeySpec(key, "AES"); + break; + case "AES-192-CBC": + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + key = new byte[24]; + System.arraycopy(round1Digest, 0, key, 0, 16); + System.arraycopy(round2Digest, 0, key, 16, 8); + secretKey = new SecretKeySpec(key, "AES"); + break; + case "AES-128-CBC": + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + key = new byte[16]; + System.arraycopy(round1Digest, 0, key, 0, 16); + secretKey = new SecretKeySpec(key, "AES"); + break; + case "DES-EDE3-CBC": + cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); + key = new byte[24]; + System.arraycopy(round1Digest, 0, key, 0, 16); + System.arraycopy(round2Digest, 0, key, 16, 8); + secretKey = new SecretKeySpec(key, "DESede"); + break; + case "DES-CBC": + cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + key = new byte[8]; + System.arraycopy(round1Digest, 0, key, 0, 8); + secretKey = new SecretKeySpec(key, "DES"); + break; + } + if (cipher != null) { + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + byte[] pkcs1 = cipher.doFinal(encryptedBinaryKey); + keySpec = decodeRSAPrivatePKCS1(pkcs1); + } else { + throw new RuntimeException("Unknown Encryption algorithm!"); + } + } else { + throw new RuntimeException("Wrong encryption details!"); + } } else { - PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); + encodedKey = encodedKey.replaceAll(".*BEGIN.*PRIVATE KEY.*", "") + .replaceAll(".*END.*PRIVATE KEY.*", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(encodedKey); + if (password == null || password.isEmpty()) { + keySpec = new PKCS8EncodedKeySpec(decoded); + } else { + PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); - EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey); - String algorithmName = privateKeyInfo.getAlgName(); - Cipher cipher = Cipher.getInstance(algorithmName); - SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName); + EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decoded); + String algorithmName = privateKeyInfo.getAlgName(); + Cipher cipher = Cipher.getInstance(algorithmName); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName); - Key pbeKey = secretKeyFactory.generateSecret(pbeKeySpec); - AlgorithmParameters algParams = privateKeyInfo.getAlgParameters(); - cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams); - keySpec = privateKeyInfo.getKeySpec(cipher); + Key pbeKey = secretKeyFactory.generateSecret(pbeKeySpec); + AlgorithmParameters algParams = privateKeyInfo.getAlgParameters(); + cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams); + keySpec = privateKeyInfo.getKeySpec(cipher); + } } return keySpec; } + + private static BigInteger derint(ByteBuffer input) { + int len = der(input, 0x02); + byte[] value = new byte[len]; + input.get(value); + return new BigInteger(+1, value); + } + + private static int der(ByteBuffer input, int exp) { + int tag = input.get() & 0xFF; + if (tag != exp) throw new IllegalArgumentException("Unexpected tag"); + int n = input.get() & 0xFF; + if (n < 128) return n; + n &= 0x7F; + if ((n < 1) || (n > 2)) throw new IllegalArgumentException("Invalid length"); + int len = 0; + while (n-- > 0) { + len <<= 8; + len |= input.get() & 0xFF; + } + return len; + } + + static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded) { + ByteBuffer input = ByteBuffer.wrap(encoded); + if (der(input, 0x30) != input.remaining()) throw new IllegalArgumentException("Excess data"); + if (!BigInteger.ZERO.equals(derint(input))) throw new IllegalArgumentException("Unsupported version"); + BigInteger n = derint(input); + BigInteger e = derint(input); + BigInteger d = derint(input); + BigInteger p = derint(input); + BigInteger q = derint(input); + BigInteger ep = derint(input); + BigInteger eq = derint(input); + BigInteger c = derint(input); + return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c); + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index d59ffee3ea..201b998129 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -17,9 +17,11 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.ListenableFuture; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.api.TbContext; @@ -29,15 +31,14 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.query.EntityKeyType; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; +import org.thingsboard.server.dao.alarm.AlarmOperationResult; import java.util.ArrayList; import java.util.Comparator; @@ -116,8 +117,16 @@ class AlarmState { for (AlarmRuleState state : createRulesSortedBySeverityDesc) { stateUpdate = clearAlarmState(stateUpdate, state); } - ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()); - pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm), clearState); + ListenableFuture alarmClearOperationResult = ctx.getAlarmService().clearAlarmForResult( + ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis() + ); + DonAsynchron.withCallback(alarmClearOperationResult, + result -> { + pushMsg(ctx, new TbAlarmResult(false, false, true, result.getAlarm()), clearState); + }, + throwable -> { + throw new RuntimeException(throwable); + }); currentAlarm = null; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { stateUpdate = clearAlarmState(stateUpdate, clearState); @@ -263,7 +272,9 @@ class AlarmState { if (StringUtils.isNotEmpty(alarmDetailsStr)) { for (var keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + if (entityKeyValue != null) { + alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + } } ObjectNode newDetails = JacksonUtil.newObjectNode(); newDetails.put("data", alarmDetailsStr); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java index 708782b16d..c53be9852a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java @@ -32,7 +32,7 @@ public class EntitiesAlarmOriginatorIdAsyncLoader { case ALARM: return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original)); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java index 10246affd8..84ed5aff8d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -42,7 +42,7 @@ public class EntitiesCustomerIdAsyncLoader { case DEVICE: return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) original)); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java index fbf7271f53..f5e4ad2662 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java @@ -63,7 +63,7 @@ public class EntitiesFieldsAsyncLoader { return getAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), (EntityViewId) original), EntityFieldsData::new); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java index 12cdc1923f..ef83c09e59 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java @@ -50,7 +50,7 @@ public class EntitiesTenantIdAsyncLoader { case RULE_CHAIN: return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync(ctx.getTenantId(), (RuleChainId) original)); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index b980423c08..6a30eb63ab 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -50,11 +50,9 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; -import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -74,7 +72,6 @@ import java.util.TreeMap; import java.util.UUID; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) @@ -386,10 +383,6 @@ public class TbDeviceProfileNodeTest { deviceProfile.setId(deviceProfileId); DeviceProfileData deviceProfileData = new DeviceProfileData(); - Device device = new Device(); - device.setId(deviceId); - device.setCustomerId(customerId); - AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" ); @@ -464,7 +457,7 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" + EntityType.CUSTOMER, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); @@ -474,7 +467,7 @@ public class TbDeviceProfileNodeTest { AttributeKvEntry entry = attributeKvEntity.toData(); ListenableFuture> listListenableFutureWithLess = - Futures.immediateFuture(Collections.singletonList(entry)); + Futures.immediateFuture(Collections.emptyList()); ListenableFuture> optionalListenableFutureWithLess = Futures.immediateFuture(Optional.of(entry)); @@ -538,10 +531,6 @@ public class TbDeviceProfileNodeTest { DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfileData deviceProfileData = new DeviceProfileData(); - Device device = new Device(); - device.setId(deviceId); - device.setCustomerId(customerId); - AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" ); @@ -553,7 +542,7 @@ public class TbDeviceProfileNodeTest { AttributeKvEntry entry = attributeKvEntity.toData(); ListenableFuture> listListenableFutureWithLess = - Futures.immediateFuture(Collections.singletonList(entry)); + Futures.immediateFuture(Collections.emptyList()); ListenableFuture> optionalListenableFutureWithLess = Futures.immediateFuture(Optional.of(entry)); @@ -620,6 +609,10 @@ public class TbDeviceProfileNodeTest { EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "tenantAttribute" ); + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); attributeKvEntity.setLongValue(100L); @@ -627,7 +620,11 @@ public class TbDeviceProfileNodeTest { AttributeKvEntry entry = attributeKvEntity.toData(); ListenableFuture> listListenableFutureWithLess = - Futures.immediateFuture(Collections.singletonList(entry)); + Futures.immediateFuture(Collections.emptyList()); + ListenableFuture> emptyOptionalFuture = + Futures.immediateFuture(Optional.empty()); + ListenableFuture> optionalListenableFutureWithLess = + Futures.immediateFuture(Optional.of(entry)); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter(); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); @@ -661,8 +658,14 @@ public class TbDeviceProfileNodeTest { Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) .thenAnswer(AdditionalAnswers.returnsFirstArg()); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(emptyOptionalFuture); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) @@ -677,6 +680,7 @@ public class TbDeviceProfileNodeTest { verify(ctx).tellSuccess(msg); verify(ctx).tellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } @@ -688,9 +692,13 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "customerAttribute" + EntityType.DEVICE, deviceId.getId(), EntityKeyType.SERVER_ATTRIBUTE.name(), "tenantAttribute" ); + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); attributeKvEntity.setLongValue(100L); @@ -698,7 +706,9 @@ public class TbDeviceProfileNodeTest { AttributeKvEntry entry = attributeKvEntity.toData(); ListenableFuture> listListenableFutureWithLess = - Futures.immediateFuture(Collections.singletonList(entry)); + Futures.immediateFuture(Collections.emptyList()); + ListenableFuture> emptyOptionalFuture = + Futures.immediateFuture(Optional.empty()); ListenableFuture> optionalListenableFutureWithLess = Futures.immediateFuture(Optional.of(entry)); @@ -711,7 +721,7 @@ public class TbDeviceProfileNodeTest { new FilterPredicateValue<>( 0.0, null, - new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "customerAttribute", true)) + new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "tenantAttribute", true)) ); lowTempFilter.setPredicate(lowTempPredicate); AlarmCondition alarmCondition = new AlarmCondition(); @@ -720,7 +730,7 @@ public class TbDeviceProfileNodeTest { alarmRule.setCondition(alarmCondition); DeviceProfileAlarm dpa = new DeviceProfileAlarm(); dpa.setId("lesstempID"); - dpa.setAlarmType("lessTemperatureAlarm"); + dpa.setAlarmType("greaterTemperatureAlarm"); dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); deviceProfileData.setAlarms(Collections.singletonList(dpa)); @@ -729,14 +739,18 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) .thenReturn(Futures.immediateFuture(Collections.emptyList())); - Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "greaterTemperatureAlarm")) .thenReturn(Futures.immediateFuture(null)); Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) .thenAnswer(AdditionalAnswers.returnsFirstArg()); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(emptyOptionalFuture); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); diff --git a/tools/pom.xml b/tools/pom.xml index c7fcf277ea..3c1294143b 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -56,8 +56,8 @@ cassandra-all - com.datastax.oss - java-driver-core + com.datastax.cassandra + cassandra-driver-core commons-io @@ -70,7 +70,7 @@ maven-assembly-plugin - + org.thingsboard.client.tools.migrator.MigratorTool diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java new file mode 100644 index 0000000000..53caeaeb0d --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class DictionaryParser { + private Map dictionaryParsed = new HashMap<>(); + + public DictionaryParser(File sourceFile) throws IOException { + parseDictionaryDump(FileUtils.lineIterator(sourceFile)); + } + + public String getKeyByKeyId(String keyId) { + return dictionaryParsed.get(keyId); + } + + private boolean isBlockFinished(String line) { + return StringUtils.isBlank(line) || line.equals("\\."); + } + + private boolean isBlockStarted(String line) { + return line.startsWith("COPY public.ts_kv_dictionary ("); + } + + private void parseDictionaryDump(LineIterator iterator) { + try { + String tempLine; + while (iterator.hasNext()) { + tempLine = iterator.nextLine(); + + if (isBlockStarted(tempLine)) { + processBlock(iterator); + } + } + } finally { + iterator.close(); + } + } + + private void processBlock(LineIterator lineIterator) { + String tempLine; + String[] lineSplited; + while(lineIterator.hasNext()) { + tempLine = lineIterator.nextLine(); + if(isBlockFinished(tempLine)) { + return; + } + + lineSplited = tempLine.split("\t"); + dictionaryParsed.put(lineSplited[1], lineSplited[0]); + } + } +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java index e3a98e974f..526d59f936 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java @@ -30,17 +30,26 @@ public class MigratorTool { public static void main(String[] args) { CommandLine cmd = parseArgs(args); - try { - File latestSource = new File(cmd.getOptionValue("latestTelemetryFrom")); - File latestSaveDir = new File(cmd.getOptionValue("latestTelemetryOut")); - File tsSource = new File(cmd.getOptionValue("telemetryFrom")); - File tsSaveDir = new File(cmd.getOptionValue("telemetryOut")); - File partitionsSaveDir = new File(cmd.getOptionValue("partitionsOut")); boolean castEnable = Boolean.parseBoolean(cmd.getOptionValue("castEnable")); + File allTelemetrySource = new File(cmd.getOptionValue("telemetryFrom")); + File tsSaveDir = null; + File partitionsSaveDir = null; + File latestSaveDir = null; + + RelatedEntitiesParser allEntityIdsAndTypes = + new RelatedEntitiesParser(new File(cmd.getOptionValue("relatedEntities"))); + DictionaryParser dictionaryParser = new DictionaryParser(allTelemetrySource); + + if(cmd.getOptionValue("latestTelemetryOut") != null) { + latestSaveDir = new File(cmd.getOptionValue("latestTelemetryOut")); + } + if(cmd.getOptionValue("telemetryOut") != null) { + tsSaveDir = new File(cmd.getOptionValue("telemetryOut")); + partitionsSaveDir = new File(cmd.getOptionValue("partitionsOut")); + } - PgCaLatestMigrator.migrateLatest(latestSource, latestSaveDir, castEnable); - PostgresToCassandraTelemetryMigrator.migrateTs(tsSource, tsSaveDir, partitionsSaveDir, castEnable); + new PgCaMigrator(allTelemetrySource, tsSaveDir, partitionsSaveDir, latestSaveDir, allEntityIdsAndTypes, dictionaryParser, castEnable).migrate(); } catch (Throwable th) { th.printStackTrace(); @@ -52,30 +61,30 @@ public class MigratorTool { private static CommandLine parseArgs(String[] args) { Options options = new Options(); - Option latestTsOpt = new Option("latestFrom", "latestTelemetryFrom", true, "latest telemetry source file path"); - latestTsOpt.setRequired(true); - options.addOption(latestTsOpt); + Option telemetryAllFrom = new Option("telemetryFrom", "telemetryFrom", true, "telemetry source file"); + telemetryAllFrom.setRequired(true); + options.addOption(telemetryAllFrom); Option latestTsOutOpt = new Option("latestOut", "latestTelemetryOut", true, "latest telemetry save dir"); - latestTsOutOpt.setRequired(true); + latestTsOutOpt.setRequired(false); options.addOption(latestTsOutOpt); - Option tsOpt = new Option("tsFrom", "telemetryFrom", true, "telemetry source file path"); - tsOpt.setRequired(true); - options.addOption(tsOpt); - Option tsOutOpt = new Option("tsOut", "telemetryOut", true, "sstable save dir"); - tsOutOpt.setRequired(true); + tsOutOpt.setRequired(false); options.addOption(tsOutOpt); Option partitionOutOpt = new Option("partitionsOut", "partitionsOut", true, "partitions save dir"); - partitionOutOpt.setRequired(true); + partitionOutOpt.setRequired(false); options.addOption(partitionOutOpt); Option castOpt = new Option("castEnable", "castEnable", true, "cast String to Double if possible"); castOpt.setRequired(true); options.addOption(castOpt); + Option relatedOpt = new Option("relatedEntities", "relatedEntities", true, "related entities source file path"); + relatedOpt.setRequired(true); + options.addOption(relatedOpt); + HelpFormatter formatter = new HelpFormatter(); CommandLineParser parser = new BasicParser(); diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java deleted file mode 100644 index 5f0a84c7f4..0000000000 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.client.tools.migrator; - -import com.google.common.collect.Lists; -import org.apache.cassandra.io.sstable.CQLSSTableWriter; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.LineIterator; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -public class PgCaLatestMigrator { - - private static final long LOG_BATCH = 1000000; - private static final long rowPerFile = 1000000; - - - private static long linesProcessed = 0; - private static long linesMigrated = 0; - private static long castErrors = 0; - private static long castedOk = 0; - - private static long currentWriterCount = 1; - private static CQLSSTableWriter currentTsWriter = null; - - public static void migrateLatest(File sourceFile, File outDir, boolean castStringsIfPossible) throws IOException { - long startTs = System.currentTimeMillis(); - long stepLineTs = System.currentTimeMillis(); - long stepOkLineTs = System.currentTimeMillis(); - LineIterator iterator = FileUtils.lineIterator(sourceFile); - currentTsWriter = WriterBuilder.getTsWriter(outDir); - - boolean isBlockStarted = false; - boolean isBlockFinished = false; - - String line; - while (iterator.hasNext()) { - if (linesProcessed++ % LOG_BATCH == 0) { - System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); - stepLineTs = System.currentTimeMillis(); - } - - line = iterator.nextLine(); - - if (isBlockFinished) { - break; - } - - if (!isBlockStarted) { - if (isBlockStarted(line)) { - System.out.println(); - System.out.println(); - System.out.println(line); - System.out.println(); - System.out.println(); - isBlockStarted = true; - } - continue; - } - - if (isBlockFinished(line)) { - isBlockFinished = true; - } else { - try { - List raw = Arrays.stream(line.trim().split("\t")) - .map(String::trim) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.toList()); - List values = toValues(raw); - - if (currentWriterCount == 0) { - System.out.println(new Date() + " close writer " + new Date()); - currentTsWriter.close(); - currentTsWriter = WriterBuilder.getLatestWriter(outDir); - } - - if (castStringsIfPossible) { - currentTsWriter.addRow(castToNumericIfPossible(values)); - } else { - currentTsWriter.addRow(values); - } - currentWriterCount++; - if (currentWriterCount >= rowPerFile) { - currentWriterCount = 0; - } - - if (linesMigrated++ % LOG_BATCH == 0) { - System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs)); - stepOkLineTs = System.currentTimeMillis(); - } - } catch (Exception ex) { - System.out.println(ex.getMessage() + " -> " + line); - } - - } - } - - long endTs = System.currentTimeMillis(); - System.out.println(); - System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); - - currentTsWriter.close(); - System.out.println(); - System.out.println("Finished migrate Latest Telemetry"); - } - - - private static List castToNumericIfPossible(List values) { - try { - if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { - Double casted = NumberUtils.createDouble(values.get(6).toString()); - List numeric = Lists.newArrayList(); - numeric.addAll(values); - numeric.set(6, null); - numeric.set(8, casted); - castedOk++; - return numeric; - } - } catch (Throwable th) { - castErrors++; - } - return values; - } - - private static List toValues(List raw) { - //expected Table structure: -// COPY public.ts_kv_latest (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; - - - List result = new ArrayList<>(); - result.add(raw.get(0)); - result.add(fromString(raw.get(1))); - result.add(raw.get(2)); - - long ts = Long.parseLong(raw.get(3)); - result.add(ts); - - result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); - result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); - result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); - result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); - return result; - } - - public static UUID fromString(String src) { - return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" - + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); - } - - private static boolean isBlockStarted(String line) { - return line.startsWith("COPY public.ts_kv_latest"); - } - - private static boolean isBlockFinished(String line) { - return StringUtils.isBlank(line) || line.equals("\\."); - } -} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java new file mode 100644 index 0000000000..29a0a23ad6 --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java @@ -0,0 +1,282 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import com.google.common.collect.Lists; +import org.apache.cassandra.io.sstable.CQLSSTableWriter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class PgCaMigrator { + + private final long LOG_BATCH = 1000000; + private final long rowPerFile = 1000000; + + private long linesTsMigrated = 0; + private long linesLatestMigrated = 0; + private long castErrors = 0; + private long castedOk = 0; + + private long currentWriterCount = 1; + + private final File sourceFile; + private final boolean castStringIfPossible; + + private final RelatedEntitiesParser entityIdsAndTypes; + private final DictionaryParser keyParser; + private CQLSSTableWriter currentTsWriter; + private CQLSSTableWriter currentPartitionsWriter; + private CQLSSTableWriter currentTsLatestWriter; + private final Set partitions = new HashSet<>(); + + private File outTsDir; + private File outTsLatestDir; + + public PgCaMigrator(File sourceFile, + File ourTsDir, + File outTsPartitionDir, + File outTsLatestDir, + RelatedEntitiesParser allEntityIdsAndTypes, + DictionaryParser dictionaryParser, + boolean castStringsIfPossible) { + this.sourceFile = sourceFile; + this.entityIdsAndTypes = allEntityIdsAndTypes; + this.keyParser = dictionaryParser; + this.castStringIfPossible = castStringsIfPossible; + if(outTsLatestDir != null) { + this.currentTsLatestWriter = WriterBuilder.getLatestWriter(outTsLatestDir); + this.outTsLatestDir = outTsLatestDir; + } + if(ourTsDir != null) { + this.currentTsWriter = WriterBuilder.getTsWriter(ourTsDir); + this.currentPartitionsWriter = WriterBuilder.getPartitionWriter(outTsPartitionDir); + this.outTsDir = ourTsDir; + } + } + + public void migrate() throws IOException { + boolean isTsDone = false; + boolean isLatestDone = false; + String line; + LineIterator iterator = FileUtils.lineIterator(this.sourceFile); + + try { + while(iterator.hasNext()) { + line = iterator.nextLine(); + if(!isLatestDone && isBlockLatestStarted(line)) { + System.out.println("START TO MIGRATE LATEST"); + long start = System.currentTimeMillis(); + processBlock(iterator, currentTsLatestWriter, outTsLatestDir, this::toValuesLatest); + System.out.println("TOTAL LINES MIGRATED: " + linesLatestMigrated + ", FORMING OF SSL FOR LATEST TS FINISHED WITH TIME: " + (System.currentTimeMillis() - start) + " ms."); + isLatestDone = true; + } + + if(!isTsDone && isBlockTsStarted(line)) { + System.out.println("START TO MIGRATE TS"); + long start = System.currentTimeMillis(); + processBlock(iterator, currentTsWriter, outTsDir, this::toValuesTs); + System.out.println("TOTAL LINES MIGRATED: " + linesTsMigrated + ", FORMING OF SSL FOR TS FINISHED WITH TIME: " + (System.currentTimeMillis() - start) + " ms."); + isTsDone = true; + } + } + + System.out.println("Partitions collected " + partitions.size()); + long startTs = System.currentTimeMillis(); + for (String partition : partitions) { + String[] split = partition.split("\\|"); + List values = Lists.newArrayList(); + values.add(split[0]); + values.add(UUID.fromString(split[1])); + values.add(split[2]); + values.add(Long.parseLong(split[3])); + currentPartitionsWriter.addRow(values); + } + + System.out.println(new Date() + " Migrated partitions " + partitions.size() + " in " + (System.currentTimeMillis() - startTs)); + + System.out.println(); + System.out.println("Finished migrate Telemetry"); + + } finally { + iterator.close(); + currentTsLatestWriter.close(); + currentTsWriter.close(); + currentPartitionsWriter.close(); + } + } + + private void logLinesProcessed(long lines) { + if (lines % LOG_BATCH == 0) { + System.out.println(new Date() + " lines processed = " + lines + " in, castOk " + castedOk + " castErr " + castErrors); + } + } + + private void logLinesMigrated(long lines) { + if(lines % LOG_BATCH == 0) { + System.out.println(new Date() + " lines migrated = " + lines + " in, castOk " + castedOk + " castErr " + castErrors); + } + } + + private void addTypeIdKey(List result, List raw) { + result.add(entityIdsAndTypes.getEntityType(raw.get(0))); + result.add(UUID.fromString(raw.get(0))); + result.add(keyParser.getKeyByKeyId(raw.get(1))); + } + + private void addPartitions(List result, List raw) { + long ts = Long.parseLong(raw.get(2)); + long partition = toPartitionTs(ts); + result.add(partition); + result.add(ts); + } + + private void addTimeseries(List result, List raw) { + result.add(Long.parseLong(raw.get(2))); + } + + private void addValues(List result, List raw) { + result.add(raw.get(3).equals("\\N") ? null : raw.get(3).equals("t") ? Boolean.TRUE : Boolean.FALSE); + result.add(raw.get(4).equals("\\N") ? null : raw.get(4)); + result.add(raw.get(5).equals("\\N") ? null : Long.parseLong(raw.get(5))); + result.add(raw.get(6).equals("\\N") ? null : Double.parseDouble(raw.get(6))); + result.add(raw.get(7).equals("\\N") ? null : raw.get(7)); + } + + private List toValuesTs(List raw) { + + logLinesMigrated(linesTsMigrated++); + + List result = new ArrayList<>(); + + addTypeIdKey(result, raw); + addPartitions(result, raw); + addValues(result, raw); + + processPartitions(result); + + return result; + } + + private List toValuesLatest(List raw) { + logLinesMigrated(linesLatestMigrated++); + List result = new ArrayList<>(); + + addTypeIdKey(result, raw); + addTimeseries(result, raw); + addValues(result, raw); + + return result; + } + + private long toPartitionTs(long ts) { + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); + return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).toInstant(ZoneOffset.UTC).toEpochMilli(); + } + + private void processPartitions(List values) { + String key = values.get(0) + "|" + values.get(1) + "|" + values.get(2) + "|" + values.get(3); + partitions.add(key); + } + + private void processBlock(LineIterator iterator, CQLSSTableWriter writer, File outDir, Function, List> function) { + String currentLine; + long linesProcessed = 0; + while(iterator.hasNext()) { + logLinesProcessed(linesProcessed++); + currentLine = iterator.nextLine(); + if(isBlockFinished(currentLine)) { + return; + } + + try { + List raw = Arrays.stream(currentLine.trim().split("\t")) + .map(String::trim) + .collect(Collectors.toList()); + List values = function.apply(raw); + + if (this.currentWriterCount == 0) { + System.out.println(new Date() + " close writer " + new Date()); + writer.close(); + writer = WriterBuilder.getLatestWriter(outDir); + } + + if (this.castStringIfPossible) { + writer.addRow(castToNumericIfPossible(values)); + } else { + writer.addRow(values); + } + + currentWriterCount++; + if (currentWriterCount >= rowPerFile) { + currentWriterCount = 0; + } + } catch (Exception ex) { + System.out.println(ex.getMessage() + " -> " + currentLine); + } + } + } + + private List castToNumericIfPossible(List values) { + try { + if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { + Double casted = NumberUtils.createDouble(values.get(6).toString()); + List numeric = Lists.newArrayList(); + numeric.addAll(values); + numeric.set(6, null); + numeric.set(8, casted); + castedOk++; + return numeric; + } + } catch (Throwable th) { + castErrors++; + } + + processPartitions(values); + + return values; + } + + private boolean isBlockFinished(String line) { + return StringUtils.isBlank(line) || line.equals("\\."); + } + + private boolean isBlockTsStarted(String line) { + return line.startsWith("COPY public.ts_kv ("); + } + + private boolean isBlockLatestStarted(String line) { + return line.startsWith("COPY public.ts_kv_latest ("); + } + +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java deleted file mode 100644 index 3ceab85ce3..0000000000 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.client.tools.migrator; - -import com.google.common.collect.Lists; -import org.apache.cassandra.io.sstable.CQLSSTableWriter; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.LineIterator; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; - -import java.io.File; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -public class PostgresToCassandraTelemetryMigrator { - - private static final long LOG_BATCH = 1000000; - private static final long rowPerFile = 1000000; - - - private static long linesProcessed = 0; - private static long linesMigrated = 0; - private static long castErrors = 0; - private static long castedOk = 0; - - private static long currentWriterCount = 1; - private static CQLSSTableWriter currentTsWriter = null; - private static CQLSSTableWriter currentPartitionWriter = null; - - private static Set partitions = new HashSet<>(); - - - public static void migrateTs(File sourceFile, File outTsDir, File outPartitionDir, boolean castStringsIfPossible) throws IOException { - long startTs = System.currentTimeMillis(); - long stepLineTs = System.currentTimeMillis(); - long stepOkLineTs = System.currentTimeMillis(); - LineIterator iterator = FileUtils.lineIterator(sourceFile); - currentTsWriter = WriterBuilder.getTsWriter(outTsDir); - currentPartitionWriter = WriterBuilder.getPartitionWriter(outPartitionDir); - - boolean isBlockStarted = false; - boolean isBlockFinished = false; - - String line; - while (iterator.hasNext()) { - if (linesProcessed++ % LOG_BATCH == 0) { - System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); - stepLineTs = System.currentTimeMillis(); - } - - line = iterator.nextLine(); - - if (isBlockFinished) { - break; - } - - if (!isBlockStarted) { - if (isBlockStarted(line)) { - System.out.println(); - System.out.println(); - System.out.println(line); - System.out.println(); - System.out.println(); - isBlockStarted = true; - } - continue; - } - - if (isBlockFinished(line)) { - isBlockFinished = true; - } else { - try { - List raw = Arrays.stream(line.trim().split("\t")) - .map(String::trim) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.toList()); - List values = toValues(raw); - - if (currentWriterCount == 0) { - System.out.println(new Date() + " close writer " + new Date()); - currentTsWriter.close(); - currentTsWriter = WriterBuilder.getTsWriter(outTsDir); - } - - if (castStringsIfPossible) { - currentTsWriter.addRow(castToNumericIfPossible(values)); - } else { - currentTsWriter.addRow(values); - } - processPartitions(values); - currentWriterCount++; - if (currentWriterCount >= rowPerFile) { - currentWriterCount = 0; - } - - if (linesMigrated++ % LOG_BATCH == 0) { - System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs) + " partitions = " + partitions.size()); - stepOkLineTs = System.currentTimeMillis(); - } - } catch (Exception ex) { - System.out.println(ex.getMessage() + " -> " + line); - } - - } - } - - long endTs = System.currentTimeMillis(); - System.out.println(); - System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); - System.out.println("Partitions collected " + partitions.size()); - - startTs = System.currentTimeMillis(); - for (String partition : partitions) { - String[] split = partition.split("\\|"); - List values = Lists.newArrayList(); - values.add(split[0]); - values.add(UUID.fromString(split[1])); - values.add(split[2]); - values.add(Long.parseLong(split[3])); - currentPartitionWriter.addRow(values); - } - currentPartitionWriter.close(); - endTs = System.currentTimeMillis(); - System.out.println(); - System.out.println(); - System.out.println(new Date() + " Migrated partitions " + partitions.size() + " in " + (endTs - startTs)); - - - currentTsWriter.close(); - System.out.println(); - System.out.println("Finished migrate Telemetry"); - } - - private static List castToNumericIfPossible(List values) { - try { - if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { - Double casted = NumberUtils.createDouble(values.get(6).toString()); - List numeric = Lists.newArrayList(); - numeric.addAll(values); - numeric.set(6, null); - numeric.set(8, casted); - castedOk++; - return numeric; - } - } catch (Throwable th) { - castErrors++; - } - return values; - } - - private static void processPartitions(List values) { - String key = values.get(0) + "|" + values.get(1) + "|" + values.get(2) + "|" + values.get(3); - partitions.add(key); - } - - private static List toValues(List raw) { - //expected Table structure: -// COPY public.ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; - - - List result = new ArrayList<>(); - result.add(raw.get(0)); - result.add(fromString(raw.get(1))); - result.add(raw.get(2)); - - long ts = Long.parseLong(raw.get(3)); - long partition = toPartitionTs(ts); - result.add(partition); - result.add(ts); - - result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); - result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); - result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); - result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); - return result; - } - - public static UUID fromString(String src) { - return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" - + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); - } - - private static long toPartitionTs(long ts) { - LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); - return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).toInstant(ZoneOffset.UTC).toEpochMilli(); -// return TsPartitionDate.MONTHS.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); - } - - private static boolean isBlockStarted(String line) { - return line.startsWith("COPY public.ts_kv"); - } - - private static boolean isBlockFinished(String line) { - return StringUtils.isBlank(line) || line.equals("\\."); - } - -} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md index 70c5dafcaf..a468cbd3af 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md @@ -21,37 +21,41 @@ It will generate single jar file with all required dependencies inside `target d #### Dump data from the source Postgres Database *Do not use compression if possible because Tool can only work with uncompressed file -1. Dump table `ts_kv` table: - - `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv > ts_kv.dmp` - -2. Dump table `ts_kv_latest` table: - - `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv_latest > ts_kv_latest.dmp` +1. Dump related tables that need to correct save telemetry + + `pg_dump -h localhost -U postgres -d thingsboard -T admin_settings -T attribute_kv -T audit_log -T component_discriptor -T device_credentials -T event -T oauth2_client_registration -T oauth2_client_registration_info -T oauth2_client_registration_template -T relation -T rule_node_state tb_schema_settings -T user_credentials > related_entities.dmp` + +2. Dump `ts_kv` and child: + + `pg_dump -h localhost -U postgres -d thingsboard --load-via-partition-root --data-only -t ts_kv* > ts_kv_all.dmp` -3. [Optional] move table dumps to the instance where cassandra will be hosted +3. [Optional] Move table dumps to the instance where cassandra will be hosted #### Prepare directory structure for SSTables Tool use 3 different directories for saving SSTables - `ts_kv_cf`, `ts_kv_latest_cf`, `ts_kv_partitions_cf` Create 3 empty directories. For example: - /home/ubunut/migration/ts - /home/ubunut/migration/ts_latest - /home/ubunut/migration/ts_partition + /home/user/migration/ts + /home/user/migration/ts_latest + /home/user/migration/ts_partition #### Run tool -*Note: if you run this tool on remote instance - don't forget to execute this command in `screen` to avoid unexpected termination + +**If you want to migrate just `ts_kv` without `ts_kv_latest` or vice versa don't use arguments (paths) for output files* + +**Note: if you run this tool on remote instance - don't forget to execute this command in `screen` to avoid unexpected termination* ``` -java -jar ./tools-2.4.1-SNAPSHOT-jar-with-dependencies.jar - -latestFrom ./source/ts_kv_latest.dmp - -latestOut /home/ubunut/migration/ts_latest - -tsFrom ./source/ts_kv.dmp - -tsOut /home/ubunut/migration/ts - -partitionsOut /home/ubunut/migration/ts_partition - -castEnable false +java -jar ./tools-3.2.2-SNAPSHOT-jar-with-dependencies.jar + -telemetryFrom /home/user/dump/ts_kv_all.dmp + -relatedEntities /home/user/dump/related_entities.dmp + -latestOut /home/user/migration/ts_latest + -tsOut /home/user/migration/ts + -partitionsOut /home/user/migration/ts_partition + -castEnable false ``` +*Use your paths for program arguments* Tool execution time depends on DB size, CPU resources and Disk throughput @@ -59,22 +63,23 @@ Tool execution time depends on DB size, CPU resources and Disk throughput * Note that this this part works only for single node Cassandra Cluster. If you have more nodes - it is better to use `sstableloader` tool. 1. [Optional] install Cassandra on the instance -2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this file `schema-ts.cql` +2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this files `schema-ts.cql` and `schema-ts-latest.cql` using `source` command 3. Stop Cassandra -4. Copy generated SSTable files into cassandra data dir: +4. Look at `/var/lib/cassandra/data/thingsboard` and check for names of data folders +5. Copy generated SSTable files into cassandra data dir using next command: ``` - sudo find /home/ubunut/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; - sudo find /home/ubunut/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; - sudo find /home/ubunut/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; + sudo find /home/user/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; + sudo find /home/user/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; + sudo find /home/user/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; ``` - -5. Start Cassandra service and trigger compaction + *Pay attention! Data folders have similar name `ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13`, but you have to use own* +6. Start Cassandra service and trigger compaction + + Trigger compactions: `nodetool compact thingsboard` + + Check compaction status: `nodetool compactionstats` -``` - trigger compactions: nodetool compact thingsboard - check compaction status: nodetool compactionstats -``` ## Switch Thignsboard into Hybrid Mode diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java new file mode 100644 index 0000000000..7c1b29f2af --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.EntityType; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class RelatedEntitiesParser { + private final Map allEntityIdsAndTypes = new HashMap<>(); + + private final Map tableNameAndEntityType = Map.ofEntries( + Map.entry("COPY public.alarm ", EntityType.ALARM), + Map.entry("COPY public.asset ", EntityType.ASSET), + Map.entry("COPY public.customer ", EntityType.CUSTOMER), + Map.entry("COPY public.dashboard ", EntityType.DASHBOARD), + Map.entry("COPY public.device ", EntityType.DEVICE), + Map.entry("COPY public.rule_chain ", EntityType.RULE_CHAIN), + Map.entry("COPY public.rule_node ", EntityType.RULE_NODE), + Map.entry("COPY public.tenant ", EntityType.TENANT), + Map.entry("COPY public.tb_user ", EntityType.USER), + Map.entry("COPY public.entity_view ", EntityType.ENTITY_VIEW), + Map.entry("COPY public.widgets_bundle ", EntityType.WIDGETS_BUNDLE), + Map.entry("COPY public.widget_type ", EntityType.WIDGET_TYPE), + Map.entry("COPY public.tenant_profile ", EntityType.TENANT_PROFILE), + Map.entry("COPY public.device_profile ", EntityType.DEVICE_PROFILE), + Map.entry("COPY public.api_usage_state ", EntityType.API_USAGE_STATE) + ); + + public RelatedEntitiesParser(File source) throws IOException { + processAllTables(FileUtils.lineIterator(source)); + } + + public String getEntityType(String uuid) { + return this.allEntityIdsAndTypes.get(uuid); + } + + private boolean isBlockFinished(String line) { + return StringUtils.isBlank(line) || line.equals("\\."); + } + + private void processAllTables(LineIterator lineIterator) { + String currentLine; + try { + while (lineIterator.hasNext()) { + currentLine = lineIterator.nextLine(); + for(Map.Entry entry : tableNameAndEntityType.entrySet()) { + if(currentLine.startsWith(entry.getKey())) { + processBlock(lineIterator, entry.getValue()); + } + } + } + } finally { + lineIterator.close(); + } + } + + private void processBlock(LineIterator lineIterator, EntityType entityType) { + String currentLine; + while(lineIterator.hasNext()) { + currentLine = lineIterator.nextLine(); + if(isBlockFinished(currentLine)) { + return; + } + allEntityIdsAndTypes.put(currentLine.split("\t")[0], entityType.name()); + } + } +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java index cfe36aaa14..5228da98a3 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java @@ -31,6 +31,7 @@ public class WriterBuilder { " str_v text,\n" + " long_v bigint,\n" + " dbl_v double,\n" + + " json_v text,\n" + " PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)\n" + ");"; @@ -43,6 +44,7 @@ public class WriterBuilder { " str_v text,\n" + " long_v bigint,\n" + " dbl_v double,\n" + + " json_v text,\n" + " PRIMARY KEY (( entity_type, entity_id ), key)\n" + ") WITH compaction = { 'class' : 'LeveledCompactionStrategy' };"; @@ -59,8 +61,8 @@ public class WriterBuilder { return CQLSSTableWriter.builder() .inDirectory(dir) .forTable(tsSchema) - .using("INSERT INTO thingsboard.ts_kv_cf (entity_type, entity_id, key, partition, ts, bool_v, str_v, long_v, dbl_v) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") + .using("INSERT INTO thingsboard.ts_kv_cf (entity_type, entity_id, key, partition, ts, bool_v, str_v, long_v, dbl_v, json_v) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") .build(); } @@ -68,8 +70,8 @@ public class WriterBuilder { return CQLSSTableWriter.builder() .inDirectory(dir) .forTable(latestSchema) - .using("INSERT INTO thingsboard.ts_kv_latest_cf (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + .using("INSERT INTO thingsboard.ts_kv_latest_cf (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") .build(); } diff --git a/tools/src/main/shell/client.keygen.sh b/tools/src/main/shell/client.keygen.sh index dcc6d30d46..981160a2a2 100755 --- a/tools/src/main/shell/client.keygen.sh +++ b/tools/src/main/shell/client.keygen.sh @@ -44,7 +44,8 @@ done . $PROPERTIES_FILE -if [ -f $CLIENT_FILE_PREFIX.jks ] || [ -f $CLIENT_FILE_PREFIX.pub.pem ] || [ -f $CLIENT_FILE_PREFIX.nopass.pem ] || [ -f $CLIENT_FILE_PREFIX.pem ] || [ -f $CLIENT_FILE_PREFIX.p12 ]; +if [ -f $CLIENT_FILE_PREFIX.jks ] || [ -f $CLIENT_FILE_PREFIX.pub.pem ] || [ -f $CLIENT_FILE_PREFIX.nopass.pem ] || \ + [ -f $CLIENT_FILE_PREFIX.pem ] || [ -f $CLIENT_FILE_PREFIX.p12 ] || [ -f $CLIENT_FILE_PREFIX.pk8.pem ]; then while : do @@ -62,6 +63,7 @@ while : rm -rf $CLIENT_FILE_PREFIX.nopass.pem rm -rf $CLIENT_FILE_PREFIX.pem rm -rf $CLIENT_FILE_PREFIX.p12 + rm -rf $CLIENT_FILE_PREFIX.pk8.pem break; ;; *) echo "Please reply 'yes' or 'no'" @@ -84,6 +86,8 @@ if [ -z "$OPENSSL_CMD" ]; then exit 0 fi +echo "INFO: your hostname is $(hostname)" +echo "INFO: your CN (domain suffix) for key is $DOMAIN_SUFFIX" echo "Generating SSL Key Pair..." keytool -genkeypair -v \ @@ -112,7 +116,15 @@ echo "Converting pkcs12 to pem" openssl pkcs12 -in $CLIENT_FILE_PREFIX.p12 \ -out $CLIENT_FILE_PREFIX.pem \ -passin pass:$CLIENT_KEY_PASSWORD \ - -passout pass:$CLIENT_KEY_PASSWORD \ + -passout pass:$CLIENT_KEY_PASSWORD + +echo "Converting pem to pkcs8" +openssl pkcs8 \ + -topk8 \ + -nocrypt \ + -in $CLIENT_FILE_PREFIX.pem \ + -out $CLIENT_FILE_PREFIX.pk8.pem \ + -passin pass:$CLIENT_KEY_PASSWORD echo "Importing server public key to $CLIENT_FILE_PREFIX.jks" keytool --importcert \ diff --git a/tools/src/main/shell/keygen.properties b/tools/src/main/shell/keygen.properties index d2733e5b7b..4808f05b2f 100644 --- a/tools/src/main/shell/keygen.properties +++ b/tools/src/main/shell/keygen.properties @@ -1,5 +1,5 @@ # -# Copyright © 2016-2017 The Thingsboard Authors +# Copyright © 2016-2021 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,15 +18,15 @@ DOMAIN_SUFFIX="$(hostname)" SUBJECT_ALTERNATIVE_NAMES="ip:127.0.0.1" ORGANIZATIONAL_UNIT=Thingsboard ORGANIZATION=Thingsboard -CITY=SF +CITY="San Francisco" STATE_OR_PROVINCE=CA TWO_LETTER_COUNTRY_CODE=US -SERVER_KEYSTORE_PASSWORD=server_ks_password -SERVER_KEY_PASSWORD=server_key_password +SERVER_KEYSTORE_PASSWORD=password +SERVER_KEY_PASSWORD=password SERVER_KEY_ALIAS="serveralias" -SERVER_FILE_PREFIX="mqttserver" +SERVER_FILE_PREFIX="server" SERVER_KEY_ALG="RSA" SERVER_KEY_SIZE="2048" SERVER_KEYSTORE_DIR="/etc/thingsboard/conf" @@ -35,6 +35,6 @@ CLIENT_KEYSTORE_PASSWORD=password CLIENT_KEY_PASSWORD=password CLIENT_KEY_ALIAS="clientalias" -CLIENT_FILE_PREFIX="mqttclient" +CLIENT_FILE_PREFIX="client" CLIENT_KEY_ALG="RSA" CLIENT_KEY_SIZE="2048" diff --git a/tools/src/main/shell/server.keygen.sh b/tools/src/main/shell/server.keygen.sh index 7679cbd812..259e7faec1 100755 --- a/tools/src/main/shell/server.keygen.sh +++ b/tools/src/main/shell/server.keygen.sh @@ -60,7 +60,8 @@ fi . $PROPERTIES_FILE -if [ -f $SERVER_FILE_PREFIX.jks ] || [ -f $SERVER_FILE_PREFIX.cer ] || [ -f $SERVER_FILE_PREFIX.pub.pem ] || [ -f $SERVER_FILE_PREFIX.pub.der ]; +if [ -f $SERVER_FILE_PREFIX.jks ] || [ -f $SERVER_FILE_PREFIX.cer ] || [ -f $SERVER_FILE_PREFIX.pub.pem ] || \ + [ -f $SERVER_FILE_PREFIX.p12 ] || [ -f $SERVER_FILE_PREFIX.pem ] || [ -f $SERVER_FILE_PREFIX.pk8.pem ] ; then while : do @@ -76,6 +77,9 @@ while : rm -rf $SERVER_FILE_PREFIX.jks rm -rf $SERVER_FILE_PREFIX.pub.pem rm -rf $SERVER_FILE_PREFIX.cer + rm -rf $SERVER_FILE_PREFIX.p12 + rm -rf $SERVER_FILE_PREFIX.pem + rm -rf $SERVER_FILE_PREFIX.pk8.pem break; ;; *) echo "Please reply 'yes' or 'no'" @@ -84,6 +88,8 @@ while : done fi +echo "INFO: your hostname is $(hostname)" +echo "INFO: your CN (domain suffix) for key is $DOMAIN_SUFFIX" echo "Generating SSL Key Pair..." EXT="" @@ -121,6 +127,32 @@ keytool -export \ -storepass $SERVER_KEYSTORE_PASSWORD \ -keypass $SERVER_KEY_PASSWORD +echo "Converting keystore to pkcs12" +keytool -importkeystore \ + -srckeystore $SERVER_FILE_PREFIX.jks \ + -destkeystore $SERVER_FILE_PREFIX.p12 \ + -srcalias $SERVER_KEY_ALIAS \ + -srcstoretype jks \ + -deststoretype pkcs12 \ + -srcstorepass $SERVER_KEYSTORE_PASSWORD \ + -deststorepass $SERVER_KEY_PASSWORD \ + -srckeypass $SERVER_KEY_PASSWORD \ + -destkeypass $SERVER_KEY_PASSWORD + +echo "Converting pkcs12 to pem" +openssl pkcs12 -in $SERVER_FILE_PREFIX.p12 \ + -out $SERVER_FILE_PREFIX.pem \ + -passin pass:$SERVER_KEY_PASSWORD \ + -passout pass:$SERVER_KEY_PASSWORD + +echo "Converting pem to pkcs8" +openssl pkcs8 \ + -topk8 \ + -nocrypt \ + -in $SERVER_FILE_PREFIX.pem \ + -out $SERVER_FILE_PREFIX.pk8.pem \ + -passin pass:$SERVER_KEY_PASSWORD + status=$? if [[ $status != 0 ]]; then exit $status; diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 2b0889b164..a9fe673b28 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -73,11 +73,11 @@ queue: security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" other: topic-properties: - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" aws_sqs: use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 585bf50483..7fc93371f6 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -67,11 +67,11 @@ queue: security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" other: topic-properties: - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" aws_sqs: use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 2ea16b2487..d641463177 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -95,11 +95,11 @@ queue: security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" other: topic-properties: - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" aws_sqs: use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index c7db10025d..7a8df1f704 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -130,6 +130,7 @@ export class AlarmDataSubscription { this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs; } + this.subscriber.setTsOffset(this.subsTw.tsOffset); this.subscriber.subscriptionCommands.push(this.alarmDataCommand); this.subscriber.alarmData$.subscribe((alarmDataUpdate) => { @@ -143,8 +144,11 @@ export class AlarmDataSubscription { this.subscriber.subscribe(); } else if (this.datasourceType === DatasourceType.function) { + const alarm = deepClone(simulatedAlarm); + alarm.createdTime += this.subsTw.tsOffset; + alarm.startTs += this.subsTw.tsOffset; const pageData: PageData = { - data: [{...simulatedAlarm, entityId: '1', latest: {}}], + data: [{...alarm, entityId: '1', latest: {}}], hasNext: false, totalElements: 1, totalPages: 1 diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 4437538719..02c1c8649a 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -15,10 +15,17 @@ /// import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; -import { AggregationType } from '@shared/models/time/time.models'; +import { + AggregationType, calculateIntervalComparisonEndTime, + calculateIntervalEndTime, calculateIntervalStartEndTime, + getCurrentTime, + getCurrentTimeForComparison, getTime, + SubscriptionTimewindow +} from '@shared/models/time/time.models'; import { UtilsService } from '@core/services/utils.service'; import { deepClone } from '@core/utils'; import Timeout = NodeJS.Timeout; +import * as moment_ from 'moment'; export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; @@ -28,8 +35,56 @@ interface AggData { aggValue: any; } -interface AggregationMap { - [key: string]: Map; +class AggDataMap { + rangeChanged = false; + private minTs = Number.MAX_SAFE_INTEGER; + private map = new Map(); + + set(ts: number, data: AggData) { + if (ts < this.minTs) { + this.rangeChanged = true; + this.minTs = ts; + } + this.map.set(ts, data); + } + + get(ts: number): AggData { + return this.map.get(ts); + } + + delete(ts: number) { + this.map.delete(ts); + } + + forEach(callback: (value: AggData, key: number, map: Map) => void, thisArg?: any) { + this.map.forEach(callback, thisArg); + } + + size(): number { + return this.map.size; + } +} + +class AggregationMap { + aggMap: {[key: string]: AggDataMap} = {}; + + detectRangeChanged(): boolean { + let changed = false; + for (const key of Object.keys(this.aggMap)) { + const aggDataMap = this.aggMap[key]; + if (aggDataMap.rangeChanged) { + changed = true; + aggDataMap.rangeChanged = false; + } + } + return changed; + } + + clearRangeChangedFlags() { + for (const key of Object.keys(this.aggMap)) { + this.aggMap[key].rangeChanged = false; + } + } } declare type AggFunction = (aggData: AggData, value?: any) => void; @@ -73,33 +128,29 @@ export class DataAggregator { private resetPending = false; private updatedData = false; - private noAggregation = this.aggregationType === AggregationType.NONE; - private aggregationTimeout = Math.max(this.interval, 1000); + private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE; + private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); private readonly aggFunction: AggFunction; private intervalTimeoutHandle: Timeout; private intervalScheduledTime: number; + private startTs: number; private endTs: number; private elapsed: number; constructor(private onDataCb: onAggregatedData, private tsKeyNames: string[], - private startTs: number, - private limit: number, - private aggregationType: AggregationType, - private timeWindow: number, - private interval: number, - private stateData: boolean, + private subsTw: SubscriptionTimewindow, private utils: UtilsService, private ignoreDataUpdateOnIntervalTick: boolean) { this.tsKeyNames.forEach((key) => { this.dataBuffer[key] = []; }); - if (this.stateData) { + if (this.subsTw.aggregation.stateData) { this.lastPrevKvPairData = {}; } - switch (this.aggregationType) { + switch (this.subsTw.aggregation.type) { case AggregationType.MIN: this.aggFunction = min; break; @@ -129,18 +180,16 @@ export class DataAggregator { return prevOnDataCb; } - public reset(startTs: number, timeWindow: number, interval: number) { + public reset(subsTw: SubscriptionTimewindow) { if (this.intervalTimeoutHandle) { clearTimeout(this.intervalTimeoutHandle); this.intervalTimeoutHandle = null; } + this.subsTw = subsTw; this.intervalScheduledTime = this.utils.currentPerfTime(); - this.startTs = startTs; - this.timeWindow = timeWindow; - this.interval = interval; - this.endTs = this.startTs + this.timeWindow; + this.calculateStartEndTs(); this.elapsed = 0; - this.aggregationTimeout = Math.max(this.interval, 1000); + this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); this.resetPending = true; this.updatedData = false; this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); @@ -155,19 +204,20 @@ export class DataAggregator { } public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) { + this.updatedData = true; if (!this.dataReceived || this.resetPending) { let updateIntervalScheduledTime = true; if (!this.dataReceived) { this.elapsed = 0; this.dataReceived = true; - this.endTs = this.startTs + this.timeWindow; + this.calculateStartEndTs(); } if (this.resetPending) { this.resetPending = false; updateIntervalScheduledTime = false; } if (update) { - this.aggregationMap = {}; + this.aggregationMap = new AggregationMap(); this.updateAggregatedData(data.data); } else { this.aggregationMap = this.processAggregatedData(data.data); @@ -175,18 +225,38 @@ export class DataAggregator { if (updateIntervalScheduledTime) { this.intervalScheduledTime = this.utils.currentPerfTime(); } + this.aggregationMap.clearRangeChangedFlags(); this.onInterval(history, detectChanges); } else { this.updateAggregatedData(data.data); if (history) { this.intervalScheduledTime = this.utils.currentPerfTime(); this.onInterval(history, detectChanges); + } else { + if (this.aggregationMap.detectRangeChanged()) { + this.onInterval(false, detectChanges, true); + } } } - this.updatedData = true; } - private onInterval(history?: boolean, detectChanges?: boolean) { + private calculateStartEndTs() { + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; + if (this.subsTw.quickInterval) { + if (this.subsTw.timeForComparison === 'previousInterval') { + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone); + const currentDate = getCurrentTime(this.subsTw.timezone); + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, startDate, currentDate) + this.subsTw.tsOffset; + } else { + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone); + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, startDate, this.subsTw.timezone) + this.subsTw.tsOffset; + } + } else { + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; + } + } + + private onInterval(history?: boolean, detectChanges?: boolean, rangeChanged?: boolean) { const now = this.utils.currentPerfTime(); this.elapsed += now - this.intervalScheduledTime; this.intervalScheduledTime = now; @@ -194,13 +264,21 @@ export class DataAggregator { clearTimeout(this.intervalTimeoutHandle); this.intervalTimeoutHandle = null; } + const intervalTimeout = rangeChanged ? this.aggregationTimeout - this.elapsed : this.aggregationTimeout; if (!history) { - const delta = Math.floor(this.elapsed / this.interval); - if (delta || !this.data) { - this.startTs += delta * this.interval; - this.endTs += delta * this.interval; + const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); + if (delta || !this.data || rangeChanged) { + const tickTs = delta * this.subsTw.aggregation.interval; + if (this.subsTw.quickInterval) { + const startEndTime = calculateIntervalStartEndTime(this.subsTw.quickInterval, this.subsTw.timezone); + this.startTs = startEndTime[0] + this.subsTw.tsOffset; + this.endTs = startEndTime[1] + this.subsTw.tsOffset; + } else { + this.startTs += tickTs; + this.endTs += tickTs; + } this.data = this.updateData(); - this.elapsed = this.elapsed - delta * this.interval; + this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval; } } else { this.data = this.updateData(); @@ -210,7 +288,7 @@ export class DataAggregator { this.updatedData = false; } if (!history) { - this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); + this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), intervalTimeout); } } @@ -218,28 +296,28 @@ export class DataAggregator { this.tsKeyNames.forEach((key) => { this.dataBuffer[key] = []; }); - for (const key of Object.keys(this.aggregationMap)) { - const aggKeyData = this.aggregationMap[key]; + for (const key of Object.keys(this.aggregationMap.aggMap)) { + const aggKeyData = this.aggregationMap.aggMap[key]; let keyData = this.dataBuffer[key]; aggKeyData.forEach((aggData, aggTimestamp) => { - if (aggTimestamp <= this.startTs) { - if (this.stateData && + if (aggTimestamp < this.startTs) { + if (this.subsTw.aggregation.stateData && (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; } aggKeyData.delete(aggTimestamp); this.updatedData = true; - } else if (aggTimestamp <= this.endTs) { + } else if (aggTimestamp < this.endTs) { const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; keyData.push(kvPair); } }); keyData.sort((set1, set2) => set1[0] - set2[0]); - if (this.stateData) { + if (this.subsTw.aggregation.stateData) { this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); } - if (keyData.length > this.limit) { - keyData = keyData.slice(keyData.length - this.limit); + if (keyData.length > this.subsTw.aggregation.limit) { + keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit); } this.dataBuffer[key] = keyData; } @@ -275,13 +353,13 @@ export class DataAggregator { } private processAggregatedData(data: SubscriptionData): AggregationMap { - const isCount = this.aggregationType === AggregationType.COUNT; - const aggregationMap: AggregationMap = {}; + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; + const aggregationMap = new AggregationMap(); for (const key of Object.keys(data)) { - let aggKeyData = aggregationMap[key]; + let aggKeyData = aggregationMap.aggMap[key]; if (!aggKeyData) { - aggKeyData = new Map(); - aggregationMap[key] = aggKeyData; + aggKeyData = new AggDataMap(); + aggregationMap.aggMap[key] = aggKeyData; } const keyData = data[key]; keyData.forEach((kvPair) => { @@ -300,19 +378,20 @@ export class DataAggregator { } private updateAggregatedData(data: SubscriptionData) { - const isCount = this.aggregationType === AggregationType.COUNT; + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; for (const key of Object.keys(data)) { - let aggKeyData = this.aggregationMap[key]; + let aggKeyData = this.aggregationMap.aggMap[key]; if (!aggKeyData) { - aggKeyData = new Map(); - this.aggregationMap[key] = aggKeyData; + aggKeyData = new AggDataMap(); + this.aggregationMap.aggMap[key] = aggKeyData; } const keyData = data[key]; keyData.forEach((kvPair) => { const timestamp = kvPair[0]; const value = this.convertValue(kvPair[1]); const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + - Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2); + Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) * + this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2); let aggData = aggKeyData.get(aggTimestamp); if (!aggData) { aggData = { @@ -340,5 +419,12 @@ export class DataAggregator { } } -} + private getCurrentTime() { + if (this.subsTw.timeForComparison) { + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone); + } else { + return getCurrentTime(this.subsTw.timezone); + } + } +} diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index dbc9bd2adb..9c769f3777 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -15,7 +15,7 @@ /// import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; -import { AggregationType, SubscriptionTimewindow } from '@shared/models/time/time.models'; +import { AggregationType, getCurrentTime, SubscriptionTimewindow } from '@shared/models/time/time.models'; import { EntityData, EntityDataPageLink, @@ -74,6 +74,7 @@ export interface EntityDataSubscriptionOptions { keyFilters?: Array; additionalKeyFilters?: Array; subscriptionTimewindow?: SubscriptionTimewindow; + latestTsOffset?: number; } export class EntityDataSubscription { @@ -95,6 +96,7 @@ export class EntityDataSubscription { private entityDataResolveSubject: Subject; private pageData: PageData; private subsTw: SubscriptionTimewindow; + private latestTsOffset: number; private dataAggregators: Array; private dataKeys: {[key: string]: Array | SubscriptionDataKey} = {}; private datasourceData: {[index: number]: {[key: string]: DataSetHolder}}; @@ -177,6 +179,7 @@ export class EntityDataSubscription { this.started = true; this.dataResolved = true; this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && @@ -238,6 +241,11 @@ export class EntityDataSubscription { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { this.prepareSubscriptionCommands(this.dataCommand); + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + this.subscriber.setTsOffset(this.subsTw.tsOffset); + } else { + this.subscriber.setTsOffset(this.latestTsOffset); + } } this.subscriber.subscriptionCommands.push(this.dataCommand); @@ -256,8 +264,8 @@ export class EntityDataSubscription { if (this.started) { const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && - !this.history && this.tsFields.length) { - const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription(); + !this.history && this.tsFields.length) { + const newSubsTw = this.listener.updateRealtimeSubscription(); this.subsTw = newSubsTw; targetCommand.tsCmd.startTs = this.subsTw.startTs; targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; @@ -266,18 +274,29 @@ export class EntityDataSubscription { targetCommand.tsCmd.agg = this.subsTw.aggregation.type; targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; this.dataAggregators.forEach((dataAggregator) => { - dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); + dataAggregator.reset(newSubsTw); }); } + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + this.subscriber.setTsOffset(this.subsTw.tsOffset); + } else { + this.subscriber.setTsOffset(this.latestTsOffset); + } targetCommand.query = this.dataCommand.query; this.subscriber.subscriptionCommands = [targetCommand]; } else { this.subscriber.subscriptionCommands = [this.dataCommand]; } }); - this.subscriber.subscribe(); } else if (this.datasourceType === DatasourceType.function) { + let tsOffset = 0; + if (this.entityDataSubscriptionOptions.type === widgetType.latest) { + tsOffset = this.entityDataSubscriptionOptions.latestTsOffset; + } else if (this.entityDataSubscriptionOptions.subscriptionTimewindow) { + tsOffset = this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + } + const entityData: EntityData = { entityId: { id: NULL_UUID, @@ -288,7 +307,7 @@ export class EntityDataSubscription { }; const name = DatasourceType.function; entityData.latest[EntityKeyType.ENTITY_FIELD] = { - name: {ts: Date.now(), value: name} + name: {ts: Date.now() + tsOffset, value: name} }; const pageData: PageData = { data: [entityData], @@ -298,7 +317,9 @@ export class EntityDataSubscription { }; this.onPageData(pageData); } else if (this.datasourceType === DatasourceType.entityCount) { + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; this.subscriber = new TelemetrySubscriber(this.telemetryService); + this.subscriber.setTsOffset(this.latestTsOffset); this.countCommand = new EntityCountCmd(); let keyFilters = this.entityDataSubscriptionOptions.keyFilters; if (this.entityDataSubscriptionOptions.additionalKeyFilters) { @@ -331,13 +352,13 @@ export class EntityDataSubscription { latest: { [EntityKeyType.ENTITY_FIELD]: { name: { - ts: Date.now(), + ts: Date.now() + this.latestTsOffset, value: DatasourceType.entityCount } }, [EntityKeyType.COUNT]: { [countKey.name]: { - ts: Date.now(), + ts: Date.now() + this.latestTsOffset, value: entityCountUpdate.count + '' } } @@ -358,7 +379,7 @@ export class EntityDataSubscription { latest: { [EntityKeyType.COUNT]: { [countKey.name]: { - ts: Date.now(), + ts: Date.now() + this.latestTsOffset, value: entityCountUpdate.count + '' } } @@ -383,6 +404,7 @@ export class EntityDataSubscription { return; } this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && @@ -394,10 +416,26 @@ export class EntityDataSubscription { this.subsCommand = new EntityDataCmd(); this.subsCommand.cmdId = this.dataCommand.cmdId; this.prepareSubscriptionCommands(this.subsCommand); - if (!this.subsCommand.isEmpty()) { + let latestTsOffsetChanged = false; + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + this.subscriber.setTsOffset(this.subsTw.tsOffset); + } else { + latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset); + } + if (latestTsOffsetChanged) { + if (this.listener.forceReInit) { + this.listener.forceReInit(); + } + } else if (!this.subsCommand.isEmpty()) { this.subscriber.subscriptionCommands = [this.subsCommand]; this.subscriber.update(); } + } else if (this.datasourceType === DatasourceType.entityCount) { + if (this.subscriber.setTsOffset(this.latestTsOffset)) { + if (this.listener.forceReInit) { + this.listener.forceReInit(); + } + } } else if (this.datasourceType === DatasourceType.function) { this.startFunction(); } @@ -745,12 +783,7 @@ export class EntityDataSubscription { this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); }, tsKeyNames, - subsTw.startTs, - subsTw.aggregation.limit, - subsTw.aggregation.type, - subsTw.aggregation.timeWindow, - subsTw.aggregation.interval, - subsTw.aggregation.stateData, + subsTw, this.utils, this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick ); @@ -786,7 +819,7 @@ export class EntityDataSubscription { } else { prevSeries = [0, 0]; } - const time = Date.now(); + const time = Date.now() + this.latestTsOffset; const value = dataKey.func(time, prevSeries[1]); const series: [number, any] = [time, value]; this.datasourceData[0][dataKey.key].data = [series]; @@ -827,7 +860,8 @@ export class EntityDataSubscription { startTime = dataKey.lastUpdateTime + this.frequency; endTime = dataKey.lastUpdateTime + deltaElapsed; } else { - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs; + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; @@ -835,8 +869,14 @@ export class EntityDataSubscription { } } } else { - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs; - endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + } + if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) { + const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + endTime = Math.min(currentTime, endTime); } } generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index 6466ba918f..1567334d0a 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -32,6 +32,7 @@ import { Observable, of } from 'rxjs'; export interface EntityDataListener { subscriptionType: widgetType; subscriptionTimewindow?: SubscriptionTimewindow; + latestTsOffset?: number; configDatasource: Datasource; configDatasourceIndex: number; dataLoaded: (pageData: PageData, @@ -39,6 +40,7 @@ export interface EntityDataListener { datasourceIndex: number, pageLink: EntityDataPageLink) => void; dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; initialPageDataChanged?: (nextPageData: PageData) => void; + forceReInit?: () => void; updateRealtimeSubscription?: () => SubscriptionTimewindow; setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void; subscriptionOptions?: EntityDataSubscriptionOptions; @@ -60,6 +62,16 @@ export class EntityDataService { constructor(private telemetryService: TelemetryWebsocketService, private utils: UtilsService) {} + private static isUnresolvedDatasource(datasource: Datasource, pageLink: EntityDataPageLink): boolean { + if (datasource.type === DatasourceType.entity) { + return !datasource.entityFilter || !pageLink; + } else if (datasource.type === DatasourceType.entityCount) { + return !datasource.entityFilter; + } else { + return false; + } + } + public prepareSubscription(listener: EntityDataListener, ignoreDataUpdateOnIntervalTick = false): Observable { const datasource = listener.configDatasource; @@ -71,7 +83,7 @@ export class EntityDataService { null, false, ignoreDataUpdateOnIntervalTick); - if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) { + if (EntityDataService.isUnresolvedDatasource(datasource, datasource.pageLink)) { return of(null); } listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); @@ -82,6 +94,8 @@ export class EntityDataService { if (listener.subscription) { if (listener.subscriptionType === widgetType.timeseries) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); + } else if (listener.subscriptionType === widgetType.latest) { + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } listener.subscription.start(); } @@ -100,7 +114,7 @@ export class EntityDataService { keyFilters, true, ignoreDataUpdateOnIntervalTick); - if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { + if (EntityDataService.isUnresolvedDatasource(datasource, pageLink)) { listener.dataLoaded(emptyPageData(), [], listener.configDatasourceIndex, listener.subscriptionOptions.pageLink); return of(null); @@ -108,6 +122,8 @@ export class EntityDataService { listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); if (listener.subscriptionType === widgetType.timeseries) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); + } else if (listener.subscriptionType === widgetType.latest) { + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } return listener.subscription.subscribe(); } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index eff95a6f2d..fc95490b0d 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -209,6 +209,7 @@ export interface WidgetSubscriptionCallbacks { onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void; onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData) => void; + forceReInit?: () => void; dataLoading?: (subscription: IWidgetSubscription) => void; legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; @@ -269,6 +270,7 @@ export interface IWidgetSubscription { hiddenData?: Array<{data: DataSet}>; timeWindowConfig?: Timewindow; timeWindow?: WidgetTimewindow; + comparisonEnabled?: boolean; comparisonTimeWindow?: WidgetTimewindow; alarms?: PageData; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index ce36f57e10..b74a807729 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -37,10 +37,13 @@ import { } from '@app/shared/models/widget.models'; import { HttpErrorResponse } from '@angular/common/http'; import { + calculateIntervalStartEndTime, + calculateTsOffset, ComparisonDuration, createSubscriptionTimewindow, createTimewindowForComparison, + isHistoryTypeTimewindow, SubscriptionTimewindow, - Timewindow, + Timewindow, timewindowTypeChanged, toHistoryTimewindow, WidgetTimewindow } from '@app/shared/models/time/time.models'; @@ -77,8 +80,10 @@ export class WidgetSubscription implements IWidgetSubscription { timeWindow: WidgetTimewindow; originalTimewindow: Timewindow; timeWindowConfig: Timewindow; + timezone: string; subscriptionTimewindow: SubscriptionTimewindow; useDashboardTimewindow: boolean; + tsOffset = 0; hasDataPageLink: boolean; singleEntity: boolean; @@ -101,7 +106,7 @@ export class WidgetSubscription implements IWidgetSubscription { decimals: number; units: string; comparisonEnabled: boolean; - timeForComparison: moment_.unitOfTime.DurationConstructor; + timeForComparison: ComparisonDuration; comparisonTimeWindow: WidgetTimewindow; timewindowForComparison: SubscriptionTimewindow; @@ -192,6 +197,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {}); this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {}); + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {}); this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); @@ -211,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription { this.timeWindow = {}; this.useDashboardTimewindow = options.useDashboardTimewindow; this.stateData = options.stateData; + if (this.type === widgetType.latest) { + this.timezone = options.dashboardTimewindow.timezone; + this.updateTsOffset(); + } if (this.useDashboardTimewindow) { this.timeWindowConfig = deepClone(options.dashboardTimewindow); } else { @@ -218,7 +228,7 @@ export class WidgetSubscription implements IWidgetSubscription { } this.subscriptionTimewindow = null; - this.comparisonEnabled = options.comparisonEnabled; + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig); if (this.comparisonEnabled) { this.timeForComparison = options.timeForComparison; @@ -408,20 +418,13 @@ export class WidgetSubscription implements IWidgetSubscription { this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true); }, initialPageDataChanged: this.initialPageDataChanged.bind(this), + forceReInit: this.forceReInit.bind(this), dataUpdated: this.dataUpdated.bind(this), updateRealtimeSubscription: () => { - if (this.comparisonEnabled && datasource.isAdditional) { - return this.updateSubscriptionForComparison(); - } else { - return this.updateRealtimeSubscription(); - } + return this.updateRealtimeSubscription(); }, setRealtimeSubscription: (subscriptionTimewindow) => { - if (this.comparisonEnabled && datasource.isAdditional) { - this.updateSubscriptionForComparison(subscriptionTimewindow); - } else { - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); - } + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); } }; this.entityDataListeners.push(listener); @@ -574,13 +577,19 @@ export class WidgetSubscription implements IWidgetSubscription { if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { if (this.useDashboardTimewindow) { if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow); this.timeWindowConfig = deepClone(newDashboardTimewindow); + this.update(isTimewindowTypeChanged); + } + } + } else if (this.type === widgetType.latest) { + if (newDashboardTimewindow && this.timezone !== newDashboardTimewindow.timezone) { + this.timezone = newDashboardTimewindow.timezone; + if (this.updateTsOffset()) { this.update(); - return true; } } } - return false; } updateDataVisibility(index: number): void { @@ -599,8 +608,9 @@ export class WidgetSubscription implements IWidgetSubscription { updateTimewindowConfig(newTimewindow: Timewindow): void { if (!this.useDashboardTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow); this.timeWindowConfig = newTimewindow; - this.update(); + this.update(isTimewindowTypeChanged); } } @@ -609,10 +619,11 @@ export class WidgetSubscription implements IWidgetSubscription { this.ctx.dashboardTimewindowApi.onResetTimewindow(); } else { if (this.originalTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow); this.timeWindowConfig = deepClone(this.originalTimewindow); this.originalTimewindow = null; this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.update(); + this.update(isTimewindowTypeChanged); } } } @@ -626,7 +637,8 @@ export class WidgetSubscription implements IWidgetSubscription { } this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService); this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.update(); + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow); + this.update(isTimewindowTypeChanged); } } @@ -755,16 +767,20 @@ export class WidgetSubscription implements IWidgetSubscription { } } - update() { + update(isTimewindowTypeChanged = false) { if (this.type !== widgetType.rpc) { if (this.type === widgetType.alarm) { this.updateAlarmDataSubscription(); } else { - if (this.hasDataPageLink) { - this.updateDataSubscriptions(); + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) { + this.forceReInit(); } else { - this.notifyDataLoading(); - this.dataSubscribe(); + if (this.hasDataPageLink) { + this.updateDataSubscriptions(); + } else { + this.notifyDataLoading(); + this.dataSubscribe(); + } } } } @@ -813,6 +829,7 @@ export class WidgetSubscription implements IWidgetSubscription { configDatasource: datasource, configDatasourceIndex: datasourceIndex, subscriptionTimewindow: this.subscriptionTimewindow, + latestTsOffset: this.tsOffset, dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); }, @@ -837,9 +854,11 @@ export class WidgetSubscription implements IWidgetSubscription { if (this.alarmDataListener) { this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); } + if (this.timeWindowConfig) { this.updateRealtimeSubscription(); } + this.alarmDataListener = { subscriptionTimewindow: this.subscriptionTimewindow, alarmSource: this.alarmSource, @@ -878,25 +897,28 @@ export class WidgetSubscription implements IWidgetSubscription { } private dataSubscribe() { + this.updateDataTimewindow(); if (!this.hasDataPageLink) { - if (this.type === widgetType.timeseries && this.timeWindowConfig) { - this.updateDataTimewindow(); - if (this.subscriptionTimewindow.fixedWindow) { + if (this.type === widgetType.timeseries && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) { this.onDataUpdated(); - } } const forceUpdate = !this.datasources.length; + const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; this.entityDataListeners.forEach((listener) => { if (this.comparisonEnabled && listener.configDatasource.isAdditional) { listener.subscriptionTimewindow = this.timewindowForComparison; } else { listener.subscriptionTimewindow = this.subscriptionTimewindow; + listener.latestTsOffset = this.tsOffset; } this.ctx.entityDataService.startSubscription(listener); }); if (forceUpdate) { this.onDataUpdated(); } + if (notifyDataLoaded) { + this.notifyDataLoaded(); + } } } @@ -1080,15 +1102,31 @@ export class WidgetSubscription implements IWidgetSubscription { private updateTimewindow() { this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; + this.timeWindow.timezone = this.subscriptionTimewindow.timezone; if (this.subscriptionTimewindow.realtimeWindowMs) { - this.timeWindow.maxTime = moment().valueOf() + this.timeWindow.stDiff; - this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; + if (this.subscriptionTimewindow.quickInterval) { + const startEndTime = calculateIntervalStartEndTime(this.subscriptionTimewindow.quickInterval, this.subscriptionTimewindow.timezone); + this.timeWindow.maxTime = startEndTime[1] + this.subscriptionTimewindow.tsOffset; + this.timeWindow.minTime = startEndTime[0] + this.subscriptionTimewindow.tsOffset; + } else { + this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff; + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; + } } else if (this.subscriptionTimewindow.fixedWindow) { - this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; - this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs + this.subscriptionTimewindow.tsOffset; + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs + this.subscriptionTimewindow.tsOffset; } } + private updateTsOffset(): boolean { + const newOffset = calculateTsOffset(this.timezone); + if (this.tsOffset !== newOffset) { + this.tsOffset = newOffset; + return true; + } + return false; + } + private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { if (subscriptionTimewindow) { this.subscriptionTimewindow = subscriptionTimewindow; @@ -1103,24 +1141,15 @@ export class WidgetSubscription implements IWidgetSubscription { private updateComparisonTimewindow() { this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; - if (this.timewindowForComparison.realtimeWindowMs) { - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); - this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs; - } else if (this.timewindowForComparison.fixedWindow) { - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; + this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; + if (this.timewindowForComparison.fixedWindow) { + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset; + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset; } } - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { - if (subscriptionTimewindow) { - this.timewindowForComparison = subscriptionTimewindow; - } else { - if (!this.subscriptionTimewindow) { - this.subscriptionTimewindow = this.updateRealtimeSubscription(); - } - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); - } + private updateSubscriptionForComparison(): SubscriptionTimewindow { + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); this.updateComparisonTimewindow(); return this.timewindowForComparison; } @@ -1129,6 +1158,10 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.onInitialPageDataChanged(this, nextPageData); } + private forceReInit() { + this.callbacks.forceReInit(); + } + private dataLoaded(pageData: PageData, data: Array>, datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) { @@ -1335,7 +1368,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.onDataUpdated(); } - private alarmsUpdated(_updated: Array, alarms: PageData) { + private alarmsUpdated(updated: Array, alarms: PageData) { this.alarmsLoaded(alarms, 0, 0); } diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index 300e12c55e..f490f90d89 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -21,11 +21,23 @@ import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { Widget, WidgetType, widgetType, widgetTypesData } from '@shared/models/widget.models'; +import { + Widget, + WidgetType, + widgetType, + WidgetTypeDetails, + WidgetTypeInfo, + widgetTypesData +} from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { ResourcesService } from '../services/resources.service'; -import { toWidgetInfo, toWidgetType, WidgetInfo } from '@app/modules/home/models/widget-component.models'; +import { + toWidgetInfo, + toWidgetType, + toWidgetTypeDetails, + WidgetInfo +} from '@app/modules/home/models/widget-component.models'; import { filter, map, mergeMap, tap } from 'rxjs/operators'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -43,6 +55,8 @@ export class WidgetService { private systemWidgetsBundles: Array; private tenantWidgetsBundles: Array; + private widgetTypeInfosCache = new Map>(); + private loadWidgetsBundleCacheSubject: ReplaySubject; constructor( @@ -117,6 +131,25 @@ export class WidgetService { defaultHttpOptionsFromConfig(config)); } + public getBundleWidgetTypesDetails(bundleAlias: string, isSystem: boolean, + config?: RequestConfig): Observable> { + return this.http.get>(`/api/widgetTypesDetails?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, + defaultHttpOptionsFromConfig(config)); + } + + public getBundleWidgetTypeInfos(bundleAlias: string, isSystem: boolean, + config?: RequestConfig): Observable> { + const key = bundleAlias + (isSystem ? '_sys' : ''); + if (this.widgetTypeInfosCache.has(key)) { + return of(this.widgetTypeInfosCache.get(key)); + } else { + return this.http.get>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, + defaultHttpOptionsFromConfig(config)).pipe( + tap((res) => this.widgetTypeInfosCache.set(key, res) ) + ); + } + } + public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, config?: RequestConfig): Observable> { return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe( @@ -176,21 +209,22 @@ export class WidgetService { defaultHttpOptionsFromConfig(config)); } - public saveWidgetType(widgetInfo: WidgetInfo, - id: WidgetTypeId, - bundleAlias: string, - config?: RequestConfig): Observable { - const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias); - return this.http.post('/api/widgetType', widgetTypeInstance, + public saveWidgetTypeDetails(widgetInfo: WidgetInfo, + id: WidgetTypeId, + bundleAlias: string, + createdTime: number, + config?: RequestConfig): Observable { + const widgetTypeDetails = toWidgetTypeDetails(widgetInfo, id, undefined, bundleAlias, createdTime); + return this.http.post('/api/widgetType', widgetTypeDetails, defaultHttpOptionsFromConfig(config)).pipe( tap((savedWidgetType) => { this.widgetTypeUpdatedSubject.next(savedWidgetType); })); } - public saveImportedWidgetType(widgetTypeInstance: WidgetType, - config?: RequestConfig): Observable { - return this.http.post('/api/widgetType', widgetTypeInstance, + public saveImportedWidgetTypeDetails(widgetTypeDetails: WidgetTypeDetails, + config?: RequestConfig): Observable { + return this.http.post('/api/widgetType', widgetTypeDetails, defaultHttpOptionsFromConfig(config)).pipe( tap((savedWidgetType) => { this.widgetTypeUpdatedSubject.next(savedWidgetType); @@ -212,8 +246,8 @@ export class WidgetService { } public getWidgetTypeById(widgetTypeId: string, - config?: RequestConfig): Observable { - return this.http.get(`/api/widgetType/${widgetTypeId}`, + config?: RequestConfig): Observable { + return this.http.get(`/api/widgetType/${widgetTypeId}`, defaultHttpOptionsFromConfig(config)); } @@ -280,6 +314,7 @@ export class WidgetService { this.systemWidgetsBundles = undefined; this.tenantWidgetsBundles = undefined; this.loadWidgetsBundleCacheSubject = undefined; + this.widgetTypeInfosCache.clear(); } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c03b10d4d1..b9c2697d0a 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -18,6 +18,8 @@ import _ from 'lodash'; import { Observable, Subject } from 'rxjs'; import { finalize, share } from 'rxjs/operators'; import { Datasource } from '@app/shared/models/widget.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; const varsRegex = /\${([^}]*)}/g; @@ -431,3 +433,7 @@ export function generateSecret(length?: number): string { } return str.concat(generateSecret(length - str.length)); } + +export function validateEntityId(entityId: EntityId): boolean { + return isDefinedAndNotNull(entityId.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId.entityType); +} diff --git a/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts b/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts index 5c3bcb8d11..1498e4a49d 100644 --- a/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts +++ b/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts @@ -44,6 +44,8 @@ export class WidgetEditorDashboardResolver implements Resolve { typeAlias: 'customWidget', type: editWidgetInfo.type, title: 'My widget', + image: null, + description: null, sizeX: editWidgetInfo.sizeX * 2, sizeY: editWidgetInfo.sizeY * 2, row: 2, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index b04c7cdf81..41b97843c1 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -18,7 +18,7 @@

widget.add

- + : {{data.widgetInfo.widgetName}}
+ +
+ + +
+
+
- diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 44ee29ce84..7e760e3337 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -18,11 +18,14 @@ import { ChangeDetectorRef, Component, Inject, + Injector, Input, NgZone, OnDestroy, OnInit, + StaticProvider, ViewChild, + ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; @@ -44,7 +47,7 @@ import { } from '@app/shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; import { WindowMessage } from '@shared/models/window-message.model'; -import { deepClone, isDefined } from '@app/core/utils'; +import { deepClone, isDefined, isDefinedAndNotNull } from '@app/core/utils'; import { DashboardContext, DashboardPageLayout, @@ -58,7 +61,15 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthState } from '@core/auth/auth.selectors'; -import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; +import { + DatasourceType, + Widget, + WidgetConfig, + WidgetInfo, + WidgetPosition, + widgetType, + widgetTypesData +} from '@shared/models/widget.models'; import { environment as env } from '@env/environment'; import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; @@ -80,7 +91,10 @@ import { import { EntityAliases } from '@app/shared/models/alias.models'; import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { AddWidgetDialogComponent, AddWidgetDialogData } from '@home/components/dashboard-page/add-widget-dialog.component'; +import { + AddWidgetDialogComponent, + AddWidgetDialogData +} from '@home/components/dashboard-page/add-widget-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { ManageDashboardLayoutsDialogComponent, @@ -99,7 +113,16 @@ import { ImportExportService } from '@home/components/import-export/import-expor import { AuthState } from '@app/core/auth/auth.models'; import { FiltersDialogComponent, FiltersDialogData } from '@home/components/filter/filters-dialog.component'; import { Filters } from '@shared/models/query/query.models'; -import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { + DISPLAY_WIDGET_TYPES_PANEL_DATA, + DisplayWidgetTypesPanelComponent, + DisplayWidgetTypesPanelData, + WidgetTypes +} from '@home/components/dashboard-page/widget-types-panel.component'; +import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component'; +import {AliasEntityType, EntityType} from "@shared/models/entity-type.models"; // @dynamic @Component({ @@ -146,7 +169,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC forceDashboardMobileMode = false; isAddingWidget = false; isAddingWidgetClosed = true; - widgetsBundle: WidgetsBundle = null; + searchBundle = ''; + filterWidgetTypes: widgetType[] = null; isToolbarOpened = false; isToolbarOpenedAnimate = false; @@ -245,6 +269,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @ViewChild('tbEditWidget') editWidgetComponent: EditWidgetComponent; + @ViewChild('dashboardWidgetSelect') dashboardWidgetSelectComponent: DashboardWidgetSelectComponent; + constructor(protected store: Store, @Inject(WINDOW) private window: Window, private breakpointObserver: BreakpointObserver, @@ -263,6 +289,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private dialog: MatDialog, private translate: TranslateService, private ngZone: NgZone, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef) { super(store); @@ -345,7 +373,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.forceDashboardMobileMode = false; this.isAddingWidget = false; this.isAddingWidgetClosed = true; - this.widgetsBundle = null; this.isToolbarOpened = false; this.isToolbarOpenedAnimate = false; @@ -862,19 +889,28 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } } - addWidgetFromType(widget: Widget) { + addWidgetFromType(widget: WidgetInfo) { this.onAddWidgetClosed(); + this.searchBundle = ''; this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe( (widgetTypeInfo) => { const config: WidgetConfig = JSON.parse(widgetTypeInfo.defaultConfig); config.title = 'New ' + widgetTypeInfo.widgetName; config.datasources = []; + if (isDefinedAndNotNull(config.alarmSource)) { + config.alarmSource = { + type: DatasourceType.entity, + dataKeys: config.alarmSource.dataKeys || [] + }; + } const newWidget: Widget = { isSystemType: widget.isSystemType, bundleAlias: widget.bundleAlias, typeAlias: widgetTypeInfo.alias, type: widgetTypeInfo.type, title: 'New widget', + image: null, + description: null, sizeX: widgetTypeInfo.sizeX, sizeY: widgetTypeInfo.sizeY, config, @@ -1121,4 +1157,61 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } return widgetContextActions; } + + widgetBundleSelected(){ + this.searchBundle = ''; + } + + clearSelectedWidgetBundle() { + this.searchBundle = ''; + this.dashboardWidgetSelectComponent.widgetsBundle = null; + } + + editWidgetsTypesToDisplay($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const providers: StaticProvider[] = [ + { + provide: DISPLAY_WIDGET_TYPES_PANEL_DATA, + useValue: { + types: Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => { + return {type, display: true}; + }), + typesUpdated: (newTypes) => { + this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type); + } + } as DisplayWidgetTypesPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(DisplayWidgetTypesPanelComponent, this.viewContainerRef, injector)); + this.cd.detectChanges(); + } + + onCloseSearchBundle() { + this.searchBundle = ''; + } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html index a095dae071..68d6c3f030 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html @@ -15,75 +15,71 @@ limitations under the License. --> -
- - - - - - - - - - - - - - - - - - widgets-bundle.empty - widget.select-widgets-bundle +
+
+
+
+ +
+ {{ widget.title }} +
+
+ {{widget.title}} + {{ 'widget.' + widget.type | translate }} + + {{ widget.description }} + +
+
+
+
+ +
+ + {{ 'widget.loading-widgets' | translate }} + + +
+ + widgets-bundle.empty + +
+
+ +
+
+ +
+ {{ widgetsBundle.title }} +
+
+ {{ widgetsBundle.title }} + widgets-bundle.system + + {{ widgetsBundle.description }} + +
+
+
+
+ +
+ + {{ 'widgets-bundle.loading-widgets-bundles' | translate }} + + +
+ + widgets-bundle.no-widgets-bundles-text + +
+
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index 5e5a196cf2..30a9dc7c2b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -13,10 +13,67 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - .mat-tab-group { - .mat-tab-body-wrapper { - height: 100%; +@import '../../../../../scss/constants'; + +:host{ + min-width: 100%; + min-height: 100%; + position: absolute; + background-color: #cfd8dc; + + .widget-select { + padding: 12px 0 12px 12px; + background-color: #cfd8dc; + + .mat-card-container { + flex: 0 0 100%; + max-width: 100%; + + &:hover { + .mat-card { + box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%) + } + } + + .mat-card { + cursor: pointer; + transition: box-shadow 0.2s; + + .preview-container { + text-align: center; + margin: auto 0; + } + + .preview { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } + + .mat-card-title { + font-size: 20px; + } + + .mat-card-content { + font-size: 14px; + line-height: 18px; + color: #666; + } + } + } + + @media #{$mat-gt-xs} { + .mat-card-container { + flex: 0 0 50%; + max-width: 50%; + } + } + + @media screen and (min-width: 2000px) { + .mat-card-container { + flex: 0 0 33.333333%; + max-width: 33.333333%; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts index 910b816ed9..c51cc9c37f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts @@ -14,120 +14,182 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { IAliasController } from '@core/api/widget-api.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetService } from '@core/http/widget.service'; -import { Widget, widgetType } from '@shared/models/widget.models'; -import { toWidgetInfo } from '@home/models/widget-component.models'; -import { DashboardCallbacks } from '../../models/dashboard-component.models'; +import { WidgetInfo, widgetType } from '@shared/models/widget.models'; +import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-dashboard-widget-select', templateUrl: './dashboard-widget-select.component.html', styleUrls: ['./dashboard-widget-select.component.scss'] }) -export class DashboardWidgetSelectComponent implements OnInit, OnChanges { +export class DashboardWidgetSelectComponent implements OnInit { - @Input() - widgetsBundle: WidgetsBundle; + private search$ = new BehaviorSubject(''); + private filterWidgetTypes$ = new BehaviorSubject>(null); + private widgetsInfo: Observable>; + private widgetsBundleValue: WidgetsBundle; + widgetTypes = new Set(); + + widgets$: Observable>; + loadingWidgetsSubject: BehaviorSubject = new BehaviorSubject(false); + loadingWidgets$ = this.loadingWidgetsSubject.pipe( + share() + ); + widgetsBundles$: Observable>; + loadingWidgetBundlesSubject: BehaviorSubject = new BehaviorSubject(true); + loadingWidgetBundles$ = this.loadingWidgetBundlesSubject.pipe( + share() + ); + + set widgetsBundle(widgetBundle: WidgetsBundle) { + if (this.widgetsBundleValue !== widgetBundle) { + this.widgetsBundleValue = widgetBundle; + if (widgetBundle === null) { + this.widgetTypes.clear(); + } + this.filterWidgetTypes$.next(null); + this.widgetsInfo = null; + } + } + + get widgetsBundle(): WidgetsBundle { + return this.widgetsBundleValue; + } @Input() aliasController: IAliasController; - @Output() - widgetSelected: EventEmitter = new EventEmitter(); + @Input() + set searchBundle(search: string) { + this.search$.next(search); + } + + @Input() + set filterWidgetTypes(widgetTypes: Array) { + this.filterWidgetTypes$.next(widgetTypes); + } - timeseriesWidgetTypes: Array = []; - latestWidgetTypes: Array = []; - rpcWidgetTypes: Array = []; - alarmWidgetTypes: Array = []; - staticWidgetTypes: Array = []; + @Output() + widgetSelected: EventEmitter = new EventEmitter(); - callbacks: DashboardCallbacks = { - onWidgetClicked: this.onWidgetClicked.bind(this) - }; + @Output() + widgetsBundleSelected: EventEmitter = new EventEmitter(); - constructor(private widgetsService: WidgetService) { + constructor(private widgetsService: WidgetService, + private sanitizer: DomSanitizer) { + this.widgetsBundles$ = this.search$.asObservable().pipe( + distinctUntilChanged(), + switchMap(search => this.fetchWidgetBundle(search)) + ); + this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable()]).pipe( + distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)), + switchMap(search => this.fetchWidget(...search)) + ); } ngOnInit(): void { } - ngOnChanges(changes: SimpleChanges): void { - for (const propName of Object.keys(changes)) { - const change = changes[propName]; - if (change.currentValue !== change.previousValue && change.currentValue) { - if (propName === 'widgetsBundle') { - this.loadLibrary(); - } + private getWidgets(): Observable> { + if (!this.widgetsInfo) { + if (this.widgetsBundle !== null) { + const bundleAlias = this.widgetsBundle.alias; + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; + this.loadingWidgetsSubject.next(true); + this.widgetsInfo = this.widgetsService.getBundleWidgetTypeInfos(bundleAlias, isSystem).pipe( + map(widgets => { + widgets = widgets.sort((a, b) => b.createdTime - a.createdTime); + const widgetTypes = new Set(); + const widgetInfos = widgets.map((widgetTypeInfo) => { + widgetTypes.add(widgetTypeInfo.widgetType); + const widget: WidgetInfo = { + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetTypeInfo.alias, + type: widgetTypeInfo.widgetType, + title: widgetTypeInfo.name, + image: widgetTypeInfo.image, + description: widgetTypeInfo.description + }; + return widget; + } + ); + setTimeout(() => { + this.widgetTypes = widgetTypes; + }); + return widgetInfos; + }), + tap(() => { + this.loadingWidgetsSubject.next(false); + }), + publishReplay(1), + refCount() + ); + } else { + this.widgetsInfo = of([]); } } + return this.widgetsInfo; } - private loadLibrary() { - this.timeseriesWidgetTypes.length = 0; - this.latestWidgetTypes.length = 0; - this.rpcWidgetTypes.length = 0; - this.alarmWidgetTypes.length = 0; - this.staticWidgetTypes.length = 0; - const bundleAlias = this.widgetsBundle.alias; - const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; - this.widgetsService.getBundleWidgetTypes(bundleAlias, - isSystem).subscribe( - (types) => { - types = types.sort((a, b) => b.createdTime - a.createdTime); - let top = 0; - types.forEach((type) => { - const widgetTypeInfo = toWidgetInfo(type); - const widget: Widget = { - typeId: type.id, - isSystemType: isSystem, - bundleAlias, - typeAlias: widgetTypeInfo.alias, - type: widgetTypeInfo.type, - title: widgetTypeInfo.widgetName, - sizeX: widgetTypeInfo.sizeX, - sizeY: widgetTypeInfo.sizeY, - row: top, - col: 0, - config: JSON.parse(widgetTypeInfo.defaultConfig) - }; - widget.config.title = widgetTypeInfo.widgetName; - switch (widgetTypeInfo.type) { - case widgetType.timeseries: - this.timeseriesWidgetTypes.push(widget); - break; - case widgetType.latest: - this.latestWidgetTypes.push(widget); - break; - case widgetType.rpc: - this.rpcWidgetTypes.push(widget); - break; - case widgetType.alarm: - this.alarmWidgetTypes.push(widget); - break; - case widgetType.static: - this.staticWidgetTypes.push(widget); - break; - } - top += widget.sizeY; - }); - } - ); + onWidgetClicked($event: Event, widget: WidgetInfo): void { + this.widgetSelected.emit(widget); } - hasWidgetTypes() { - return this.timeseriesWidgetTypes.length > 0 || - this.latestWidgetTypes.length > 0 || - this.rpcWidgetTypes.length > 0 || - this.alarmWidgetTypes.length > 0 || - this.staticWidgetTypes.length > 0; + isSystem(item: WidgetsBundle): boolean { + return item && item.tenantId.id === NULL_UUID; } - private onWidgetClicked($event: Event, widget: Widget, index: number): void { - this.widgetSelected.emit(widget); + selectBundle($event: Event, bundle: WidgetsBundle) { + $event.preventDefault(); + this.widgetsBundle = bundle; + this.search$.next(''); + this.widgetsBundleSelected.emit(bundle); + } + + getPreviewImage(imageUrl: string | null): SafeUrl | string { + if (isDefinedAndNotNull(imageUrl)) { + return this.sanitizer.bypassSecurityTrustUrl(imageUrl); + } + return '/assets/widget-preview-empty.svg'; } + private getWidgetsBundle(): Observable> { + return this.widgetsService.getAllWidgetsBundles().pipe( + tap(() => this.loadingWidgetBundlesSubject.next(false)), + publishReplay(1), + refCount() + ); + } + + private fetchWidgetBundle(search: string): Observable> { + return this.getWidgetsBundle().pipe( + map(bundles => search ? bundles.filter( + bundle => ( + bundle.title?.toLowerCase().includes(search.toLowerCase()) || + bundle.description?.toLowerCase().includes(search.toLowerCase()) + )) : bundles + ) + ); + } + + private fetchWidget(search: string, filter: widgetType[]): Observable> { + return this.getWidgets().pipe( + map(widgets => filter ? widgets.filter((widget) => filter.includes(widget.type)) : widgets), + map(widgets => search ? widgets.filter( + widget => ( + widget.title?.toLowerCase().includes(search.toLowerCase()) || + widget.description?.toLowerCase().includes(search.toLowerCase()) + )) : widgets + ) + ); + } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html new file mode 100644 index 0000000000..ebadbaacde --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html @@ -0,0 +1,23 @@ + +
+ + {{ 'widget.' + type.type | translate }} + +
diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss similarity index 63% rename from application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java rename to ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss index 7ed1a0adb4..238326a072 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss @@ -13,12 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.attributes.request.nosql; +:host { + width: 100%; + height: 100%; + min-width: 200px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; + .mat-content { + overflow: hidden; + background-color: #fff; + } - -@DaoNoSqlTest -public class MqttAttributesRequestNoSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { + .mat-padding { + padding: 16px; + } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts new file mode 100644 index 0000000000..5465f552e7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, InjectionToken } from '@angular/core'; +import { widgetType } from '@shared/models/widget.models'; + +export const DISPLAY_WIDGET_TYPES_PANEL_DATA = new InjectionToken('DisplayWidgetTypesPanelData'); + +export interface WidgetTypes { + type: widgetType; + display: boolean; +} + +export interface DisplayWidgetTypesPanelData { + types: WidgetTypes[]; + typesUpdated: (columns: WidgetTypes[]) => void; +} + +@Component({ + selector: 'tb-widget-types-panel', + templateUrl: './widget-types-panel.component.html', + styleUrls: ['./widget-types-panel.component.scss'] +}) +export class DisplayWidgetTypesPanelComponent { + + types: WidgetTypes[]; + + constructor(@Inject(DISPLAY_WIDGET_TYPES_PANEL_DATA) public data: DisplayWidgetTypesPanelData) { + this.types = this.data.types; + } + + public update() { + this.data.typesUpdated(this.types); + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index bd675a0a9f..d893ec848f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -80,9 +80,9 @@ (mousedown)="widgetMouseDown($event, widget)" (click)="widgetClicked($event, widget)" (contextmenu)="openWidgetContextMenu($event, widget)"> -
-
- +
+
-
-
- {{ headerTitle }} +
+
+ + {{ headerTitle }} +
{{ headerSubtitle }}
+
+ +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index b0e7ee8e07..4027593502 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -165,7 +165,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro let configuration: DeviceProfileTransportConfiguration = null; if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) { configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson; - configuration.type = DeviceTransportType.LWM2M; + // configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index ebbe10b481..670e6fbb72 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -65,8 +65,8 @@
- - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + {{transportPayloadTypeTranslations.get(type) | translate}} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index c01eeae4e5..a61ad841aa 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -28,11 +28,13 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { + defaultAttributesSchema, + defaultTelemetrySchema, DeviceProfileTransportConfiguration, DeviceTransportType, MqttDeviceProfileTransportConfiguration, - MqttTransportPayloadType, - mqttTransportPayloadTypeTranslationMap + TransportPayloadType, + transportPayloadTypeTranslationMap } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; @@ -48,40 +50,12 @@ import { isDefinedAndNotNull } from '@core/utils'; }) export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { - mqttTransportPayloadTypes = Object.keys(MqttTransportPayloadType); + transportPayloadTypes = Object.keys(TransportPayloadType); - mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap; + transportPayloadTypeTranslations = transportPayloadTypeTranslationMap; mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; - private defaultTelemetrySchema = - 'syntax ="proto3";\n' + - 'package telemetry;\n' + - '\n' + - 'message SensorDataReading {\n' + - '\n' + - ' double temperature = 1;\n' + - ' double humidity = 2;\n' + - ' InnerObject innerObject = 3;\n' + - '\n' + - ' message InnerObject {\n' + - ' string key1 = 1;\n' + - ' bool key2 = 2;\n' + - ' double key3 = 3;\n' + - ' int32 key4 = 4;\n' + - ' string key5 = 5;\n' + - ' }\n' + - '}\n'; - - private defaultAttributesSchema = - 'syntax ="proto3";\n' + - 'package attributes;\n' + - '\n' + - 'message SensorConfiguration {\n' + - ' string firmwareVersion = 1;\n' + - ' string serialNumber = 2;\n' + - '}'; - private requiredValue: boolean; get required(): boolean { @@ -114,9 +88,9 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], transportPayloadTypeConfiguration: this.fb.group({ - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], - deviceTelemetryProtoSchema: [this.defaultTelemetrySchema, Validators.required], - deviceAttributesProtoSchema: [this.defaultAttributesSchema, Validators.required] + transportPayloadType: [TransportPayloadType.JSON, Validators.required], + deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required], + deviceAttributesProtoSchema: [defaultAttributesSchema, Validators.required] }) }, {validator: this.uniqueDeviceTopicValidator} ); @@ -140,7 +114,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control get protoPayloadType(): boolean { const transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').value; - return transportPayloadType === MqttTransportPayloadType.PROTOBUF; + return transportPayloadType === TransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { @@ -159,16 +133,16 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, forceUpdated = false) { + private updateTransportPayloadBasedControls(type: TransportPayloadType, forceUpdated = false) { const transportPayloadTypeForm = this.mqttDeviceProfileTransportConfigurationFormGroup .get('transportPayloadTypeConfiguration') as FormGroup; if (forceUpdated) { transportPayloadTypeForm.patchValue({ - deviceTelemetryProtoSchema: this.defaultTelemetrySchema, - deviceAttributesProtoSchema: this.defaultAttributesSchema + deviceTelemetryProtoSchema: defaultTelemetrySchema, + deviceAttributesProtoSchema: defaultAttributesSchema }, {emitEvent: false}); } - if (type === MqttTransportPayloadType.PROTOBUF && !this.disabled) { + if (type === TransportPayloadType.PROTOBUF && !this.disabled) { transportPayloadTypeForm.get('deviceTelemetryProtoSchema').enable({emitEvent: false}); transportPayloadTypeForm.get('deviceAttributesProtoSchema').enable({emitEvent: false}); } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html index 363b6cae32..03e5abeb3c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html @@ -20,7 +20,7 @@
@@ -31,7 +31,7 @@
- + @@ -70,7 +70,7 @@ (click)="editDataKey(key, $index)" mat-button mat-icon-button class="tb-mat-32"> edit - close + close
@@ -91,7 +93,7 @@ [displayWith]="displayKeyFn"> - + @@ -128,7 +130,7 @@ {{ translate.get('entity.no-key-matching', {key: truncate.transform(searchText, true, 6, '...')}) | async }} - + entity.create-new-key diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index 96bd8c702f..ff7f5e2f0c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -121,7 +121,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie private requiredValue: boolean; get required(): boolean { - return this.requiredValue || !this.optDataKeys || this.datasourceType === DatasourceType.entityCount; + return this.requiredValue || !this.optDataKeys || this.isEntityCountDatasource; } @Input() set required(value: boolean) { @@ -217,7 +217,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie if (this.maxDataKeys !== null && this.maxDataKeys > -1) { if (this.datasourceType === DatasourceType.function) { return this.translate.instant('datakey.maximum-function-types', {count: this.maxDataKeys}); - } else if (this.datasourceType !== DatasourceType.entityCount) { + } else if (!this.isEntityCountDatasource) { return this.translate.instant('datakey.maximum-timeseries-or-attributes', {count: this.maxDataKeys}); } else { return ''; @@ -250,7 +250,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie private reset() { if (this.widgetType === widgetType.alarm) { this.keys = this.utils.getDefaultAlarmDataKeys(); - } else if (this.datasourceType === DatasourceType.entityCount) { + } else if (this.isEntityCountDatasource) { this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count)]; } else { this.keys = []; @@ -271,11 +271,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie const change = changes[propName]; if (!change.firstChange && change.currentValue !== change.previousValue) { if (propName === 'entityAliasId') { - this.searchText = ''; - this.fetchObservable$ = null; - this.latestSearchTextResult = null; + this.clearSearchCache(); this.dirty = true; } else if (['widgetType', 'datasourceType'].includes(propName)) { + this.clearSearchCache(); this.updateParams(); setTimeout(() => { this.reset(); @@ -436,20 +435,18 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie if (this.datasourceType === DatasourceType.function) { const targetKeysList = this.widgetType === widgetType.alarm ? this.alarmKeys : this.functionTypeKeys; fetchObservable = of(targetKeysList); - } else { - if (this.datasourceType !== DatasourceType.entityCount && this.entityAliasId) { - const dataKeyTypes = [DataKeyType.timeseries]; - if (this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) { - dataKeyTypes.push(DataKeyType.attribute); - dataKeyTypes.push(DataKeyType.entityField); - if (this.widgetType === widgetType.alarm) { - dataKeyTypes.push(DataKeyType.alarm); - } + } else if (this.datasourceType === DatasourceType.entity && this.entityAliasId) { + const dataKeyTypes = [DataKeyType.timeseries]; + if (this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) { + dataKeyTypes.push(DataKeyType.attribute); + dataKeyTypes.push(DataKeyType.entityField); + if (this.widgetType === widgetType.alarm) { + dataKeyTypes.push(DataKeyType.alarm); } - fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, dataKeyTypes); - } else { - fetchObservable = of([]); } + fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, dataKeyTypes); + } else { + fetchObservable = of([]); } this.fetchObservable$ = fetchObservable.pipe( publishReplay(1), @@ -477,4 +474,13 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie }, 0); } + get isEntityCountDatasource(): boolean { + return this.datasourceType === DatasourceType.entityCount; + } + + private clearSearchCache() { + this.searchText = ''; + this.fetchObservable$ = null; + this.latestSearchTextResult = null; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index 8855e8aa9c..9685dec25b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -120,12 +120,13 @@
- + 0) { this.defaultPageSize = pageSize; @@ -383,18 +396,21 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, keySettings.columnWidth = '120px'; } } - this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, alarm, ctx'); this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); this.contentsInfo[dataKey.def].units = dataKey.units; this.contentsInfo[dataKey.def].decimals = dataKey.decimals; this.columnWidth[dataKey.def] = getColumnWidth(keySettings); + this.columnDefaultVisibility[dataKey.def] = getColumnDefaultVisibility(keySettings); + this.columnSelectionAvailability[dataKey.def] = getColumnSelectionAvailability(keySettings); this.columns.push(dataKey); if (dataKey.type !== DataKeyType.alarm) { latestDataKeys.push(dataKey); } }); - this.displayedColumns.push(...this.columns.map(column => column.def)); + this.displayedColumns.push(...this.columns.filter(column => this.columnDefaultVisibility[column.def]) + .map(column => column.def)); } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); @@ -450,7 +466,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return { title: column.title, def: column.def, - display: this.displayedColumns.indexOf(column.def) > -1 + display: this.displayedColumns.indexOf(column.def) > -1, + selectable: this.columnSelectionAvailability[column.def] }; }); @@ -464,7 +481,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if (this.enableSelection) { this.displayedColumns.unshift('select'); } - this.displayedColumns.push('actions'); + if (this.actionCellDescriptors.length) { + this.displayedColumns.push('actions'); + } } } as DisplayColumnsPanelData }, @@ -590,6 +609,30 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return widthStyle(columnWidth); } + public rowStyle(alarm: AlarmDataInfo): any { + let style: any = {}; + if (alarm) { + if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { + try { + style = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx); + if (!isObject(style)) { + throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + } + if (Array.isArray(style)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + style = {}; + console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` + + `returns '${e}'. Please check your row style function.`); + } + } else { + style = {}; + } + } + return style; + } + public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any { let style: any = {}; if (alarm && key) { @@ -597,7 +640,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, const value = getAlarmValue(alarm, key); if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { - style = styleInfo.cellStyleFunction(value); + style = styleInfo.cellStyleFunction(value, alarm, this.ctx); if (!isObject(style)) { throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); } @@ -639,6 +682,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return ''; } else { + content = this.utils.customTranslation(content, content); switch (typeof content) { case 'string': return this.domSanitizer.bypassSecurityTrustHtml(content); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html index bb8ae1b513..3104899d04 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html @@ -17,7 +17,7 @@ -->
- {{ column.title }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index cc6a6a198b..680ed3ad97 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -47,7 +47,7 @@ [ngStyle]="cellStyle(entity, column)"> - + @@ -81,10 +81,11 @@
- + 0) { this.defaultPageSize = pageSize; @@ -297,6 +313,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni useCellStyleFunction: false }; this.columnWidth.entityName = '0px'; + this.columnDefaultVisibility.entityName = true; + this.columnSelectionAvailability.entityName = true; } if (displayEntityLabel) { this.columns.push( @@ -318,6 +336,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni useCellStyleFunction: false }; this.columnWidth.entityLabel = '0px'; + this.columnDefaultVisibility.entityLabel = true; + this.columnSelectionAvailability.entityLabel = true; } if (displayEntityType) { this.columns.push( @@ -339,6 +359,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni useCellStyleFunction: false }; this.columnWidth.entityType = '0px'; + this.columnDefaultVisibility.entityType = true; + this.columnSelectionAvailability.entityType = true; } const dataKeys: Array = []; @@ -366,14 +388,17 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } } - this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, entity, ctx'); this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); this.contentsInfo[dataKey.def].units = dataKey.units; this.contentsInfo[dataKey.def].decimals = dataKey.decimals; this.columnWidth[dataKey.def] = getColumnWidth(keySettings); + this.columnDefaultVisibility[dataKey.def] = getColumnDefaultVisibility(keySettings); + this.columnSelectionAvailability[dataKey.def] = getColumnSelectionAvailability(keySettings); this.columns.push(dataKey); }); - this.displayedColumns.push(...this.columns.map(column => column.def)); + this.displayedColumns.push(...this.columns.filter(column => this.columnDefaultVisibility[column.def]) + .map(column => column.def)); } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { @@ -420,7 +445,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni return { title: column.title, def: column.def, - display: this.displayedColumns.indexOf(column.def) > -1 + display: this.displayedColumns.indexOf(column.def) > -1, + selectable: this.columnSelectionAvailability[column.def] }; }); @@ -431,7 +457,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni columns, columnsUpdated: (newColumns) => { this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); - this.displayedColumns.push('actions'); + if (this.actionCellDescriptors.length) { + this.displayedColumns.push('actions'); + } } } as DisplayColumnsPanelData }, @@ -507,6 +535,30 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni return widthStyle(columnWidth); } + public rowStyle(entity: EntityData): any { + let style: any = {}; + if (entity) { + if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { + try { + style = this.rowStylesInfo.rowStyleFunction(entity, this.ctx); + if (!isObject(style)) { + throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + } + if (Array.isArray(style)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + style = {}; + console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` + + `returns '${e}'. Please check your row style function.`); + } + } else { + style = {}; + } + } + return style; + } + public cellStyle(entity: EntityData, key: EntityColumn): any { let style: any = {}; if (entity && key) { @@ -514,7 +566,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni const value = getEntityValue(entity, key); if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { - style = styleInfo.cellStyleFunction(value); + style = styleInfo.cellStyleFunction(value, entity, this.ctx); if (!isObject(style)) { throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); } @@ -556,6 +608,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni return ''; } else { + content = this.utils.customTranslation(content, content); switch (typeof content) { case 'string': return this.domSanitizer.bypassSecurityTrustHtml(content); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 3d60934c0f..836897740a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -20,6 +20,7 @@ import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models'; import * as moment_ from 'moment'; import { DataKeyType } from "@shared/models/telemetry/telemetry.models"; +import { ComparisonDuration } from '@shared/models/time/time.models'; export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; @@ -142,7 +143,7 @@ export interface TbFlotBaseSettings { export interface TbFlotComparisonSettings { comparisonEnabled: boolean; - timeForComparison: moment_.unitOfTime.DurationConstructor; + timeForComparison: ComparisonDuration; xaxisSecond: TbFlotSecondXAxisSettings; } @@ -543,7 +544,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { timeForComparison: { title: 'Time to show historical data', type: 'string', - default: 'months' + default: 'previousInterval' }, xaxisSecond: { title: 'Second X axis', @@ -576,6 +577,10 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { type: 'rc-select', multiple: false, items: [ + { + value: 'previousInterval', + label: 'Previous interval (default)' + }, { value: 'days', label: 'Day ago' @@ -586,7 +591,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { }, { value: 'months', - label: 'Month ago (default)' + label: 'Month ago' }, { value: 'years', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 4850d08723..e82b6db24c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -74,6 +74,7 @@ export class TbFlot { private readonly utils: UtilsService; private settings: TbFlotSettings; + private comparisonEnabled: boolean; private readonly tooltip: JQuery; @@ -263,29 +264,7 @@ export class TbFlot { }; } - if (this.settings.comparisonEnabled) { - const xaxis = deepClone(this.xaxis); - xaxis.position = 'top'; - if (this.settings.xaxisSecond) { - if (this.settings.xaxisSecond.showLabels === false) { - xaxis.tickFormatter = () => { - return ''; - }; - } - xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null; - xaxis.position = this.settings.xaxisSecond.axisPosition; - } - xaxis.tickLength = 0; - this.options.xaxes.push(xaxis); - - this.options.series = { - stack: false - }; - } else { - this.options.series = { - stack: this.settings.stack === true - }; - } + this.options.series = {}; this.options.crosshair = { mode: 'x' @@ -364,7 +343,6 @@ export class TbFlot { // Experimental this.animatedPie = this.settings.animatedPie === true; - } if (this.ctx.defaultSubscription) { @@ -372,10 +350,29 @@ export class TbFlot { } } - private init($element: JQuery, subscription: IWidgetSubscription) { - this.subscription = subscription; this.$element = $element; + this.subscription = subscription; + this.comparisonEnabled = this.subscription ? this.subscription.comparisonEnabled : this.settings.comparisonEnabled; + if (this.comparisonEnabled) { + const xaxis = deepClone(this.xaxis); + xaxis.position = 'top'; + if (this.settings.xaxisSecond) { + if (this.settings.xaxisSecond.showLabels === false) { + xaxis.tickFormatter = () => { + return ''; + }; + } + xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null; + xaxis.position = this.settings.xaxisSecond.axisPosition; + } + xaxis.tickLength = 0; + this.options.xaxes.push(xaxis); + + this.options.series.stack = false; + } else { + this.options.series.stack = this.settings.stack === true; + } const colors: string[] = []; this.yaxes = []; const yaxesMap: {[units: string]: TbFlotAxisOptions} = {}; @@ -387,7 +384,7 @@ export class TbFlot { this.settings.dataKeysListForLabels.forEach((item) => { item.settings = {}; }); - subscription.datasources.forEach((item) => { + this.subscription.datasources.forEach((item) => { const datasource: Datasource = { type: item.type, entityType: item.entityType, @@ -425,7 +422,7 @@ export class TbFlot { fill: keySettings.fillLines === true }; - if (this.settings.stack && !this.settings.comparisonEnabled) { + if (this.settings.stack && !this.comparisonEnabled) { series.stack = !keySettings.excludeFromStacking; } else { series.stack = false; @@ -557,7 +554,7 @@ export class TbFlot { } this.options.xaxes[0].min = this.subscription.timeWindow.minTime; this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -636,7 +633,7 @@ export class TbFlot { this.options.xaxes[0].min = this.subscription.timeWindow.minTime; this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -654,7 +651,7 @@ export class TbFlot { } else { this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -1293,7 +1290,7 @@ export class TbFlot { const results: TbFlotHoverInfo[] = [{ seriesHover: [] }]; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { results.push({ seriesHover: [] }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 027a303f81..c5b8eb21b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -24,16 +24,17 @@ import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; -import { isDefined, isEqual, isUndefined, createLabelFromDatasource, isDefinedAndNotNull } from '@core/utils'; +import { createLabelFromDatasource, isDefined, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; import { EntityType } from '@shared/models/entity-type.models'; import * as _moment from 'moment'; import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { RequestConfig } from '@core/http/http-utils'; import { AttributeService } from '@core/http/attribute.service'; import { AttributeData, AttributeScope, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; -import { forkJoin, Observable } from 'rxjs'; +import { forkJoin, Observable, Subject } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { ResizeObserver } from '@juggle/resize-observer'; +import { takeUntil } from 'rxjs/operators'; type FieldAlignment = 'row' | 'column'; @@ -106,6 +107,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni private widgetConfig: WidgetConfig; private subscription: IWidgetSubscription; private datasources: Array; + private destroy$ = new Subject(); public sources: Array = []; isVerticalAlignment: boolean; @@ -153,6 +155,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (this.formResize$) { this.formResize$.disconnect(); } + this.destroy$.next(); + this.destroy$.complete(); } private initializeConfig() { @@ -256,6 +260,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni private buildForm() { this.multipleInputFormGroup = this.fb.group({}); this.sources.forEach((source) => { + const addedFormControl = {}; + const waitFormControl = {}; for (const key of this.visibleKeys(source)) { const validators: ValidatorFn[] = []; if (key.settings.required) { @@ -277,6 +283,36 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni disabled: key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition}, validators ); + if (this.settings.showActionButtons) { + addedFormControl[key.name] = formControl; + if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) { + if (addedFormControl.hasOwnProperty(key.settings.disabledOnDataKey)) { + addedFormControl[key.settings.disabledOnDataKey].valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (!value) { + formControl.disable({emitEvent: false}); + } else { + formControl.enable({emitEvent: false}); + } + }); + } else { + waitFormControl[key.settings.disabledOnDataKey] = formControl; + } + } + + if (waitFormControl.hasOwnProperty(key.name)) { + formControl.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (!value) { + waitFormControl[key.name].disable({emitEvent: false}); + } else { + waitFormControl[key.name].enable({emitEvent: false}); + } + }); + } + } this.multipleInputFormGroup.addControl(key.formId, formControl); } }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index b082bbe35d..10e1f376f4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -24,20 +24,28 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; const tinycolor = tinycolor_; +type ColumnVisibilityOptions = 'visible' | 'hidden'; + +type ColumnSelectionOptions = 'enabled' | 'disabled'; + export interface TableWidgetSettings { enableSearch: boolean; - enableSelectColumnDisplay: boolean; + enableStickyAction: boolean; + enableStickyHeader: boolean; displayPagination: boolean; defaultPageSize: number; - defaultSortOrder: string; + useRowStyleFunction: boolean; + rowStyleFunction?: string; } export interface TableWidgetDataKeySettings { columnWidth?: string; useCellStyleFunction: boolean; - cellStyleFunction: string; + cellStyleFunction?: string; useCellContentFunction: boolean; - cellContentFunction: string; + cellContentFunction?: string; + defaultColumnVisibility?: ColumnVisibilityOptions; + columnSelectionToDisplay?: ColumnSelectionOptions; } export interface EntityData { @@ -58,6 +66,7 @@ export interface DisplayColumn { title: string; def: string; display: boolean; + selectable: boolean; } export type CellContentFunction = (...args: any[]) => string; @@ -69,13 +78,20 @@ export interface CellContentInfo { decimals?: number; } -export type CellStyleFunction = (value: any) => any; +export type CellStyleFunction = (...args: any[]) => any; export interface CellStyleInfo { useCellStyleFunction: boolean; cellStyleFunction?: CellStyleFunction; } +export type RowStyleFunction = (...args: any[]) => any; + +export interface RowStyleInfo { + useRowStyleFunction: boolean; + rowStyleFunction?: RowStyleFunction; +} + export function entityDataSortOrderFromString(strSortOrder: string, columns: EntityColumn[]): EntityDataSortOrder { if (!strSortOrder && !strSortOrder.length) { @@ -196,14 +212,35 @@ export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) { } } -export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellStyleInfo { +export function getRowStyleInfo(settings: TableWidgetSettings, ...args: string[]): RowStyleInfo { + let rowStyleFunction: RowStyleFunction = null; + let useRowStyleFunction = false; + + if (settings.useRowStyleFunction === true) { + if (isDefined(settings.rowStyleFunction) && settings.rowStyleFunction.length > 0) { + try { + rowStyleFunction = new Function(...args, settings.rowStyleFunction) as RowStyleFunction; + useRowStyleFunction = true; + } catch (e) { + rowStyleFunction = null; + useRowStyleFunction = false; + } + } + } + return { + useRowStyleFunction, + rowStyleFunction + }; +} + +export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings, ...args: string[]): CellStyleInfo { let cellStyleFunction: CellStyleFunction = null; let useCellStyleFunction = false; if (keySettings.useCellStyleFunction === true) { if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { try { - cellStyleFunction = new Function('value', keySettings.cellStyleFunction) as CellStyleFunction; + cellStyleFunction = new Function(...args, keySettings.cellStyleFunction) as CellStyleFunction; useCellStyleFunction = true; } catch (e) { cellStyleFunction = null; @@ -251,6 +288,13 @@ export function widthStyle(width: string): any { return widthStyleObj; } +export function getColumnDefaultVisibility(keySettings: TableWidgetDataKeySettings): boolean { + return !(isDefined(keySettings.defaultColumnVisibility) && keySettings.defaultColumnVisibility === 'hidden'); +} + +export function getColumnSelectionAvailability(keySettings: TableWidgetDataKeySettings): boolean { + return !(isDefined(keySettings.columnSelectionToDisplay) && keySettings.columnSelectionToDisplay === 'disabled'); +} export function constructTableCssString(widgetConfig: WidgetConfig): string { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 0b41d35785..f1c37d4cdd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -48,17 +48,17 @@ Timestamp + [ngStyle]="cellStyle(source, 0, row, row[0])"> {{ h.dataKey.label }} + [ngStyle]="cellStyle(source, h.index, row, row[h.index])"> - + @@ -92,8 +92,9 @@
- + ; public displayPagination = true; + public enableStickyHeader = true; + public enableStickyAction = true; public pageSizeOptions; public textSearchMode = false; public textSearch: string = null; @@ -129,6 +131,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI public showTimestamp = true; private dateFormatFilter: string; + private rowStylesInfo: RowStyleInfo; + private subscriptions: Subscription[] = []; private searchAction: WidgetAction = { @@ -196,11 +200,16 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); + this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; + this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true; + this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : true; this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false; this.showTimestamp = this.settings.showTimestamp !== false; this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS'; + this.rowStylesInfo = getRowStyleInfo(this.settings, 'rowData, ctx'); + const pageSize = this.settings.defaultPageSize; if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { this.defaultPageSize = pageSize; @@ -258,13 +267,15 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI }); source.displayedColumns.push(index + ''); source.rowDataTemplate[dataKey.label] = null; - source.stylesInfo.push(getCellStyleInfo(keySettings)); + source.stylesInfo.push(getCellStyleInfo(keySettings, 'value, rowData, ctx')); const cellContentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx'); cellContentInfo.units = dataKey.units; cellContentInfo.decimals = dataKey.decimals; source.contentsInfo.push(cellContentInfo); } - source.displayedColumns.push('actions'); + if (this.actionCellDescriptors.length) { + source.displayedColumns.push('actions'); + } const tsDatasource = new TimeseriesDatasource(source, this.hideEmptyLines, this.dateFormatFilter, this.datePipe); tsDatasource.dataUpdated(this.data); this.sources.push(source); @@ -376,13 +387,43 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return source.datasource.entityId; } - public cellStyle(source: TimeseriesTableSource, index: number, value: any): any { + public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow): any { + let style: any = {}; + if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { + try { + const rowData = source.rowDataTemplate; + rowData.Timestamp = row[0]; + source.header.forEach((headerInfo) => { + rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + }); + style = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx); + if (!isObject(style)) { + throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + } + if (Array.isArray(style)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + style = {}; + console.warn(`Row style function in widget ` + + `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`); + } + } + return style; + } + + public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): any { let style: any = {}; if (index > 0) { const styleInfo = source.stylesInfo[index - 1]; if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { - style = styleInfo.cellStyleFunction(value); + const rowData = source.rowDataTemplate; + rowData.Timestamp = row[0]; + source.header.forEach((headerInfo) => { + rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + }); + style = styleInfo.cellStyleFunction(value, rowData, this.ctx); if (!isObject(style)) { throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); } @@ -425,6 +466,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI if (!isDefined(content)) { return ''; } else { + content = this.utils.customTranslation(content, content); switch (typeof content) { case 'string': return this.domSanitizer.bypassSecurityTrustHtml(content); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index 5166a845f9..d714b96f4b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -105,7 +105,7 @@ export class WidgetComponentService { settingsSchema: this.utils.editWidgetInfo.settingsSchema, dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema, defaultConfig: this.utils.editWidgetInfo.defaultConfig - }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle' + }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle', undefined ); } const initSubject = new ReplaySubject(); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 8b745c480f..ea1cd70932 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -263,15 +263,6 @@
- - - - - - { this.reInit(); }, + forceReInit: () => { + this.reInit(); + }, dataLoading: (subscription) => { if (this.loadingData !== subscription.loadingData) { this.loadingData = subscription.loadingData; @@ -1004,7 +1007,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI const type = descriptor.type; const targetEntityParamName = descriptor.stateEntityParamName; let targetEntityId: EntityId; - if (descriptor.setEntityId) { + if (descriptor.setEntityId && validateEntityId(entityId)) { targetEntityId = entityId; } switch (type) { diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 9fa82c0b71..74b6496c67 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -27,7 +27,7 @@ import { WidgetControllerDescriptor, WidgetType, widgetType, - WidgetTypeDescriptor, + WidgetTypeDescriptor, WidgetTypeDetails, WidgetTypeParameters } from '@shared/models/widget.models'; import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; @@ -347,6 +347,8 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri alias: string; typeSettingsSchema?: string | any; typeDataKeySettingsSchema?: string | any; + image?: string; + description?: string; componentFactory?: ComponentFactory; } @@ -375,6 +377,8 @@ export const MissingWidgetType: WidgetInfo = { controllerScript: 'self.onInit = function() {}', settingsSchema: '{}\n', dataKeySettingsSchema: '{}\n', + image: null, + description: null, defaultConfig: '{\n' + '"title": "Widget type not found",\n' + '"datasources": [],\n' + @@ -398,6 +402,8 @@ export const ErrorWidgetType: WidgetInfo = { controllerScript: 'self.onInit = function() {}', settingsSchema: '{}\n', dataKeySettingsSchema: '{}\n', + image: null, + description: null, defaultConfig: '{\n' + '"title": "Widget failed to load",\n' + '"datasources": [],\n' + @@ -421,6 +427,13 @@ export interface WidgetTypeInstance { onDestroy?: () => void; } +export function detailsToWidgetInfo(widgetTypeDetailsEntity: WidgetTypeDetails): WidgetInfo { + const widgetInfo = toWidgetInfo(widgetTypeDetailsEntity); + widgetInfo.image = widgetTypeDetailsEntity.image; + widgetInfo.description = widgetTypeDetailsEntity.description; + return widgetInfo; +} + export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { return { widgetName: widgetTypeEntity.name, @@ -438,7 +451,18 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { }; } -export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, bundleAlias: string): WidgetType { +export function toWidgetTypeDetails(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, + bundleAlias: string, createdTime: number): WidgetTypeDetails { + const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, bundleAlias, createdTime); + const widgetTypeDetails: WidgetTypeDetails = {...widgetTypeEntity, + description: widgetInfo.description, + image: widgetInfo.image + }; + return widgetTypeDetails; +} + +export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, + bundleAlias: string, createdTime: number): WidgetType { const descriptor: WidgetTypeDescriptor = { type: widgetInfo.type, sizeX: widgetInfo.sizeX, @@ -454,6 +478,7 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: return { id, tenantId, + createdTime, bundleAlias, alias: widgetInfo.alias, name: widgetInfo.widgetName, diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index 299c1e4015..49a256dd55 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -20,7 +20,8 @@ import { checkBoxCell, DateEntityTableColumn, EntityTableColumn, - EntityTableConfig + EntityTableConfig, + HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -33,14 +34,14 @@ import { deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; -import { DeviceProfileComponent } from '../../components/profile/device-profile.component'; +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; -import { Observable } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData -} from '../../components/profile/add-device-profile-dialog.component'; +} from '@home/components/profile/add-device-profile-dialog.component'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Injectable() export class DeviceProfilesTableConfigResolver implements Resolve> { @@ -48,6 +49,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve = new EntityTableConfig(); constructor(private deviceProfileService: DeviceProfileService, + private importExport: ImportExportService, private translate: TranslateService, private datePipe: DatePipe, private dialogService: DialogService, @@ -80,6 +82,12 @@ export class DeviceProfilesTableConfigResolver implements Resolve true, + onAction: ($event, entity) => this.exportDeviceProfile($event, entity) + }, { name: this.translate.instant('device-profile.set-default'), icon: 'flag', @@ -101,7 +109,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve this.onDeviceProfileAction(action); this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; - this.config.addEntity = () => this.addDeviceProfile(); + this.config.addActionDescriptors = this.configureAddActions(); } resolve(): EntityTableConfig { @@ -110,8 +118,27 @@ export class DeviceProfilesTableConfigResolver implements Resolve { - return this.dialog.open { + const actions: Array = []; + actions.push( + { + name: this.translate.instant('device-profile.create-device-profile'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: () => this.addDeviceProfile() + }, + { + name: this.translate.instant('device-profile.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importDeviceProfile($event) + } + ); + return actions; + } + + addDeviceProfile() { + this.dialog.open(AddDeviceProfileDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], @@ -119,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve { + if (res) { + this.config.table.updateData(); + } + } + ); } setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { @@ -144,11 +177,31 @@ export class DeviceProfilesTableConfigResolver implements Resolve { + if (deviceProfile) { + this.config.table.updateData(); + } + } + ); + } + + exportDeviceProfile($event: Event, deviceProfile: DeviceProfile) { + if ($event) { + $event.stopPropagation(); + } + this.importExport.exportDeviceProfile(deviceProfile.id.id); + } + onDeviceProfileAction(action: EntityAction): boolean { switch (action.action) { case 'setDefault': this.setDefaultDeviceProfile(action.event, action.entity); return true; + case 'export': + this.exportDeviceProfile(action.event, action.entity); + return true; } return false; } diff --git a/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.html new file mode 100644 index 0000000000..c0f47c989f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.html @@ -0,0 +1,24 @@ + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts similarity index 58% rename from ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts rename to ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts index 4e24e8a112..b41d289f76 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts @@ -20,23 +20,24 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { - DeviceProfileTransportConfiguration, - DeviceTransportType, Lwm2mDeviceProfileTransportConfiguration + CoapDeviceTransportConfiguration, + DeviceTransportConfiguration, + DeviceTransportType } from '@shared/models/device.models'; @Component({ - selector: 'tb-lwm2m-device-profile-transport-configuration', - templateUrl: './lwm2m-device-profile-transport-configuration.component.html', + selector: 'tb-coap-device-transport-configuration', + templateUrl: './coap-device-transport-configuration.component.html', styleUrls: [], providers: [{ provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => Lwm2mDeviceProfileTransportConfigurationComponent), + useExisting: forwardRef(() => CoapDeviceTransportConfigurationComponent), multi: true }] }) -export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class CoapDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { - lwm2mDeviceProfileTransportConfigurationFormGroup: FormGroup; + coapDeviceTransportConfigurationFormGroup: FormGroup; private requiredValue: boolean; get required(): boolean { @@ -64,10 +65,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } ngOnInit() { - this.lwm2mDeviceProfileTransportConfigurationFormGroup = this.fb.group({ + this.coapDeviceTransportConfigurationFormGroup = this.fb.group({ configuration: [null, Validators.required] }); - this.lwm2mDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.coapDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); } @@ -75,21 +76,21 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.lwm2mDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false}); + this.coapDeviceTransportConfigurationFormGroup.disable({emitEvent: false}); } else { - this.lwm2mDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false}); + this.coapDeviceTransportConfigurationFormGroup.enable({emitEvent: false}); } } - writeValue(value: Lwm2mDeviceProfileTransportConfiguration | null): void { - this.lwm2mDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + writeValue(value: CoapDeviceTransportConfiguration | null): void { + this.coapDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } private updateModel() { - let configuration: DeviceProfileTransportConfiguration = null; - if (this.lwm2mDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.lwm2mDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - // configuration.type = DeviceTransportType.LWM2M; + let configuration: DeviceTransportConfiguration = null; + if (this.coapDeviceTransportConfigurationFormGroup.valid) { + configuration = this.coapDeviceTransportConfigurationFormGroup.getRawValue().configuration; + configuration.type = DeviceTransportType.COAP; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html index 73cceba5bc..86f6ccb97d 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html @@ -35,5 +35,11 @@ formControlName="configuration"> + + + +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index f6b353c8bf..6e83d955b8 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -33,6 +33,7 @@ import { DefaultDeviceTransportConfigurationComponent } from './data/default-dev import { DeviceTransportConfigurationComponent } from './data/device-transport-configuration.component'; import { MqttDeviceTransportConfigurationComponent } from './data/mqtt-device-transport-configuration.component'; import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device-transport-configuration.component'; +import { CoapDeviceTransportConfigurationComponent } from './data/coap-device-transport-configuration.component'; @NgModule({ declarations: [ @@ -41,6 +42,7 @@ import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device- DefaultDeviceTransportConfigurationComponent, MqttDeviceTransportConfigurationComponent, Lwm2mDeviceTransportConfigurationComponent, + CoapDeviceTransportConfigurationComponent, DeviceTransportConfigurationComponent, DeviceDataComponent, DeviceComponent, diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html index 708ce2324d..cffb9c380d 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html @@ -18,7 +18,7 @@

rulenode.add

- + : {{ruleNode.component.name}}
- +
- +
- +
+ +
+
+ + + + widget.description + + {{descriptionInput.value?.length || 0}}/255 + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 6efb88be0b..0444a2d5e9 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -20,8 +20,15 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetService } from '@core/http/widget.service'; -import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; -import { Widget, WidgetConfig, WidgetType, widgetType, widgetTypesData } from '@shared/models/widget.models'; +import { detailsToWidgetInfo, toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; +import { + Widget, + WidgetConfig, + WidgetType, + widgetType, + WidgetTypeDetails, + widgetTypesData +} from '@shared/models/widget.models'; import { ActivatedRoute, Router } from '@angular/router'; import { deepClone } from '@core/utils'; import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; @@ -108,7 +115,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe isReadOnly: boolean; widgetsBundle: WidgetsBundle; - widgetType: WidgetType; + widgetTypeDetails: WidgetTypeDetails; widget: WidgetInfo; origWidget: WidgetInfo; @@ -175,14 +182,14 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe } else { this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; } - this.widgetType = data.widgetEditorData.widgetType; + this.widgetTypeDetails = data.widgetEditorData.widgetTypeDetails; this.widget = data.widgetEditorData.widget; - if (this.widgetType) { + if (this.widgetTypeDetails) { const config = JSON.parse(this.widget.defaultConfig); this.widget.defaultConfig = JSON.stringify(config); } this.origWidget = deepClone(this.widget); - if (!this.widgetType) { + if (!this.widgetTypeDetails) { this.isDirty = true; } } @@ -515,10 +522,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe } private commitSaveWidget() { - const id = (this.widgetType && this.widgetType.id) ? this.widgetType.id : undefined; - this.widgetService.saveWidgetType(this.widget, id, this.widgetsBundle.alias).subscribe( - (widgetTypeInstance) => { - this.setWidgetType(widgetTypeInstance); + const id = (this.widgetTypeDetails && this.widgetTypeDetails.id) ? this.widgetTypeDetails.id : undefined; + const createdTime = (this.widgetTypeDetails && this.widgetTypeDetails.createdTime) ? this.widgetTypeDetails.createdTime : undefined; + this.widgetService.saveWidgetTypeDetails(this.widget, id, this.widgetsBundle.alias, createdTime).subscribe( + (widgetTypeDetails) => { + this.setWidgetTypeDetails(widgetTypeDetails); this.saveWidgetPending = false; this.store.dispatch(new ActionNotificationShow( {message: this.translate.instant('widget.widget-saved'), type: 'success', duration: 500})); @@ -543,9 +551,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe config.title = this.widget.widgetName; this.widget.defaultConfig = JSON.stringify(config); this.isDirty = false; - this.widgetService.saveWidgetType(this.widget, undefined, saveWidgetAsData.bundleAlias).subscribe( - (widgetTypeInstance) => { - this.router.navigateByUrl(`/widgets-bundles/${saveWidgetAsData.bundleId}/widgetTypes/${widgetTypeInstance.id.id}`); + this.widgetService.saveWidgetTypeDetails(this.widget, undefined, saveWidgetAsData.bundleAlias, undefined).subscribe( + (widgetTypeDetails) => { + this.router.navigateByUrl(`/widgets-bundles/${saveWidgetAsData.bundleId}/widgetTypes/${widgetTypeDetails.id.id}`); } ); } @@ -554,9 +562,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe ); } - private setWidgetType(widgetTypeInstance: WidgetType) { - this.widgetType = widgetTypeInstance; - this.widget = toWidgetInfo(this.widgetType); + private setWidgetTypeDetails(widgetTypeDetails: WidgetTypeDetails) { + this.widgetTypeDetails = widgetTypeDetails; + this.widget = detailsToWidgetInfo(this.widgetTypeDetails); const config = JSON.parse(this.widget.defaultConfig); this.widget.defaultConfig = JSON.stringify(config); this.origWidget = deepClone(this.widget); diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index a8b6d47a0e..92a499524a 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -27,14 +27,14 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetService } from '@core/http/widget.service'; import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; import { map } from 'rxjs/operators'; -import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; -import { widgetType, WidgetType } from '@app/shared/models/widget.models'; +import { detailsToWidgetInfo, toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; +import { widgetType, WidgetType, WidgetTypeDetails } from '@app/shared/models/widget.models'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { WidgetsData } from '@home/models/dashboard-component.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; export interface WidgetEditorData { - widgetType: WidgetType; + widgetTypeDetails: WidgetTypeDetails; widget: WidgetInfo; } @@ -83,8 +83,8 @@ export class WidgetEditorDataResolver implements Resolve { return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe( map((result) => { return { - widgetType: result, - widget: toWidgetInfo(result) + widgetTypeDetails: result, + widget: detailsToWidgetInfo(result) }; }) ); @@ -106,7 +106,7 @@ export class WidgetEditorAddDataResolver implements Resolve { map((widget) => { widget.widgetName = null; return { - widgetType: null, + widgetTypeDetails: null, widget }; }) diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html index 5de3e49047..88fde0e428 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html @@ -45,6 +45,16 @@ {{ 'widgets-bundle.title-required' | translate }} + + + + widgets-bundle.description + + {{descriptionInput.value?.length || 0}}/255 +
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts index c82b1b4198..4947a90e1f 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts @@ -47,12 +47,18 @@ export class WidgetsBundleComponent extends EntityComponent { buildForm(entity: WidgetsBundle): FormGroup { return this.fb.group( { - title: [entity ? entity.title : '', [Validators.required]] + title: [entity ? entity.title : '', [Validators.required]], + image: [entity ? entity.image : ''], + description: [entity ? entity.description : '', Validators.maxLength(255)] } ); } updateForm(entity: WidgetsBundle) { - this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({ + title: entity.title, + image: entity.image, + description: entity.description + }); } } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 1175881d37..3fbd055da0 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -51,7 +51,12 @@ common.password - + + lock
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index 2526cfe376..de66448c73 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -37,7 +37,8 @@ export class LoginComponent extends PageComponent implements OnInit { password: '' }); oauth2Clients: Array = null; - + hidePassword = true; + constructor(protected store: Store, private authService: AuthService, public fb: FormBuilder, diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index b5b136cc03..b3bbb861e2 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -274,7 +274,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } } ); - } else { + } else if (value.entityType && value.id) { const targetEntityType = this.checkEntityType(value.entityType); this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe( (entity) => { @@ -289,6 +289,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } } ); + } else { + this.modelValue = null; + this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false}); } } else { this.modelValue = null; diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 73f63d54cc..461b2190b9 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -122,6 +122,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte writeValue(value: EntityId | null): void { if (value != null) { + if (value.id === NULL_UUID) { + value.id = null; + } this.modelValue = value; this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); @@ -146,6 +149,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte || this.modelValue.entityType === AliasEntityType.CURRENT_USER || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; + } else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) { + this.modelValue.id = NULL_UUID; } if (this.modelValue.entityType && this.modelValue.id) { diff --git a/ui-ngx/src/app/shared/components/image-input.component.html b/ui-ngx/src/app/shared/components/image-input.component.html index 02af7162d9..4b7e4fe1d4 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.html +++ b/ui-ngx/src/app/shared/components/image-input.component.html @@ -42,4 +42,5 @@
+
dashboard.maximum-upload-file-size
diff --git a/ui-ngx/src/app/shared/components/image-input.component.scss b/ui-ngx/src/app/shared/components/image-input.component.scss index 93a2abdfe4..4f6608232c 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.scss +++ b/ui-ngx/src/app/shared/components/image-input.component.scss @@ -101,4 +101,8 @@ $previewSize: 100px !default; } } } + + .tb-hint{ + margin-top: 8px; + } } diff --git a/ui-ngx/src/app/shared/components/image-input.component.ts b/ui-ngx/src/app/shared/components/image-input.component.ts index 6a3bff9da2..3064163b89 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.ts +++ b/ui-ngx/src/app/shared/components/image-input.component.ts @@ -24,6 +24,9 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { FlowDirective } from '@flowjs/ngx-flow'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { UtilsService } from '@core/services/utils.service'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; +import { FileSizePipe } from '@shared/pipe/file-size.pipe'; @Component({ selector: 'tb-image-input', @@ -42,6 +45,9 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit, @Input() label: string; + @Input() + maxSizeByte: number; + private requiredValue: boolean; get required(): boolean { @@ -80,7 +86,10 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit, constructor(protected store: Store, private utils: UtilsService, - private sanitizer: DomSanitizer) { + private sanitizer: DomSanitizer, + private dialog: DialogService, + private translate: TranslateService, + private fileSize: FileSizePipe) { super(store); } @@ -88,6 +97,15 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit, this.autoUploadSubscription = this.flow.events$.subscribe(event => { if (event.type === 'fileAdded') { const file = (event.event[0] as flowjs.FlowFile).file; + if (this.maxSizeByte && this.maxSizeByte < file.size) { + this.dialog.alert( + this.translate.instant('dashboard.cannot-upload-file'), + this.translate.instant('dashboard.maximum-upload-file-size', {size: this.fileSize.transform(this.maxSizeByte)}) + ).subscribe( + () => { } + ); + return false; + } const reader = new FileReader(); reader.onload = (loadEvent) => { if (typeof reader.result === 'string' && reader.result.startsWith('data:image/')) { diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html new file mode 100644 index 0000000000..c9f367c574 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html @@ -0,0 +1,27 @@ + +
+ + timewindow.interval + + + {{ timeIntervalTranslationMap.get(interval) | translate}} + + + +
diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss new file mode 100644 index 0000000000..97c400525e --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + min-width: 364px; +} diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts new file mode 100644 index 0000000000..3eab8c428d --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/models/time/time.models'; + +@Component({ + selector: 'tb-quick-time-interval', + templateUrl: './quick-time-interval.component.html', + styleUrls: ['./quick-time-interval.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => QuickTimeIntervalComponent), + multi: true + } + ] +}) +export class QuickTimeIntervalComponent implements OnInit, ControlValueAccessor { + + private allIntervals = Object.values(QuickTimeInterval); + + modelValue: QuickTimeInterval; + timeIntervalTranslationMap = QuickTimeIntervalTranslationMap; + + rendered = false; + + @Input() disabled: boolean; + + @Input() onlyCurrentInterval = false; + + private propagateChange = (_: any) => {}; + + constructor() { + } + + get intervals() { + if (this.onlyCurrentInterval) { + return this.allIntervals.filter(interval => interval.startsWith('CURRENT_')); + } + return this.allIntervals; + } + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(interval: QuickTimeInterval): void { + this.modelValue = interval; + } + + onIntervalChange() { + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index e4ddaa0f56..57c596bbc2 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -21,16 +21,43 @@ -
- -
+
+
+ + +
+
+
+ + +
+ timewindow.last + +
+
+ +
+ timewindow.interval + +
+
+
+
+
+
@@ -65,6 +92,17 @@ style="padding-top: 8px;">
+ +
+ timewindow.interval + +
+
@@ -95,7 +133,7 @@ -
+
aggregation.limit @@ -139,6 +177,17 @@ predefinedName="aggregation.group-interval">
+
+
+ + +
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss new file mode 100644 index 0000000000..5d48626b01 --- /dev/null +++ b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.input-wrapper { + background: hsla(0, 0%, 100%, .2); + border-radius: 8px; + color: rgb(255, 255, 255, .84); + padding: 5px 8px 5px 16px; + height: 46px; + + input { + width: 100%; + height: 100%; + padding: 0; + font-size: 20px; + outline: none; + border: none; + background-color: transparent; + color: #fff; + + &::placeholder { + color: #fff; + opacity: .6; + line-height: 26px; + } + } + + &.focus { + background: #fff; + color: rgba(0,0,0,.54); + + input { + color: #000; + opacity: .60; + } + + .close { + color: #000 !important; + opacity: .54; + } + } +} + diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts new file mode 100644 index 0000000000..f14ae2ee5e --- /dev/null +++ b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, ElementRef, forwardRef, Input, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'tb-widgets-bundle-search', + templateUrl: './widgets-bundle-search.component.html', + styleUrls: ['./widgets-bundle-search.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => WidgetsBundleSearchComponent), + multi: true + }], + encapsulation: ViewEncapsulation.None +}) +export class WidgetsBundleSearchComponent implements ControlValueAccessor { + + searchText: string; + focus = false; + + @Input() placeholder: string; + + @ViewChild('searchInput') searchInput: ElementRef; + + private propagateChange = (v: any) => { }; + + constructor() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(value: string | null): void { + this.searchText = value; + } + + updateSearchText(): void { + this.updateView(); + } + + private updateView() { + this.propagateChange(this.searchText); + } + + clear($event: Event): void { + $event.preventDefault(); + $event.stopPropagation(); + this.searchText = ''; + this.updateView(); + } + + toggleFocus() { + this.focus = !this.focus; + } +} diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss b/ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss index 4c261cefb3..83e4ed0bcd 100644 --- a/ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss +++ b/ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss @@ -89,6 +89,7 @@ mat-toolbar { .mat-select-placeholder { color: #fff; opacity: .8; + line-height: 26px; } } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 8f2095c514..1d587ccd72 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -35,14 +35,20 @@ export enum DeviceProfileType { export enum DeviceTransportType { DEFAULT = 'DEFAULT', MQTT = 'MQTT', - LWM2M = 'LWM2M' + // LWM2M = 'LWM2M' + COAP = 'COAP' } -export enum MqttTransportPayloadType { +export enum TransportPayloadType { JSON = 'JSON', PROTOBUF = 'PROTOBUF' } +export enum CoapTransportDeviceType { + DEFAULT = 'DEFAULT', + EFENTO = 'EFENTO' +} + export enum DeviceProvisionType { DISABLED = 'DISABLED', ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES', @@ -76,7 +82,8 @@ export const deviceTransportTypeTranslationMap = new Map( [ [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], - [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + [DeviceTransportType.COAP, 'device-profile.transport-type-coap-hint'] + ] +); + +export const transportPayloadTypeTranslationMap = new Map( + [ + [TransportPayloadType.JSON, 'device-profile.transport-device-payload-type-json'], + [TransportPayloadType.PROTOBUF, 'device-profile.transport-device-payload-type-proto'] ] ); -export const mqttTransportPayloadTypeTranslationMap = new Map( +export const defaultTelemetrySchema = + 'syntax ="proto3";\n' + + 'package telemetry;\n' + + '\n' + + 'message SensorDataReading {\n' + + '\n' + + ' double temperature = 1;\n' + + ' double humidity = 2;\n' + + ' InnerObject innerObject = 3;\n' + + '\n' + + ' message InnerObject {\n' + + ' string key1 = 1;\n' + + ' bool key2 = 2;\n' + + ' double key3 = 3;\n' + + ' int32 key4 = 4;\n' + + ' string key5 = 5;\n' + + ' }\n' + + '}\n'; + +export const defaultAttributesSchema = + 'syntax ="proto3";\n' + + 'package attributes;\n' + + '\n' + + 'message SensorConfiguration {\n' + + ' string firmwareVersion = 1;\n' + + ' string serialNumber = 2;\n' + + '}'; + +export const coapDeviceTypeTranslationMap = new Map( [ - [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], - [MqttTransportPayloadType.PROTOBUF, 'device-profile.mqtt-device-payload-type-proto'] + [CoapTransportDeviceType.DEFAULT, 'device-profile.coap-device-type-default'], + [CoapTransportDeviceType.EFENTO, 'device-profile.coap-device-type-efento'] ] ); @@ -121,12 +164,19 @@ export const deviceTransportTypeConfigurationInfoMap = new Map { constructor(msg: EntityDataUpdateMsg) { super(msg); } + + public prepareData(tsOffset: number) { + if (this.data) { + this.processEntityData(this.data.data, tsOffset); + } + if (this.update) { + this.processEntityData(this.update, tsOffset); + } + } + + private processEntityData(data: Array, tsOffset: number) { + for (const entityData of data) { + if (entityData.timeseries) { + for (const key of Object.keys(entityData.timeseries)) { + const tsValues = entityData.timeseries[key]; + for (const tsValue of tsValues) { + if (tsValue.ts) { + tsValue.ts += tsOffset; + } + } + } + } + if (entityData.latest) { + for (const entityKeyType of Object.keys(entityData.latest)) { + const keyTypeValues = entityData.latest[entityKeyType]; + for (const key of Object.keys(keyTypeValues)) { + const tsValue = keyTypeValues[key]; + if (tsValue.ts) { + tsValue.ts += tsOffset; + } + if (key === entityFields.createdTime.keyName && tsValue.value) { + tsValue.value = (Number(tsValue.value) + tsOffset) + ''; + } + } + } + } + } + } } export class AlarmDataUpdate extends DataUpdate { @@ -441,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate { this.allowedEntities = msg.allowedEntities; this.totalEntities = msg.totalEntities; } + + public prepareData(tsOffset: number) { + if (this.data) { + this.processAlarmData(this.data.data, tsOffset); + } + if (this.update) { + this.processAlarmData(this.update, tsOffset); + } + } + + private processAlarmData(data: Array, tsOffset: number) { + for (const alarmData of data) { + alarmData.createdTime += tsOffset; + if (alarmData.ackTs) { + alarmData.ackTs += tsOffset; + } + if (alarmData.clearTs) { + alarmData.clearTs += tsOffset; + } + if (alarmData.endTs) { + alarmData.endTs += tsOffset; + } + if (alarmData.latest) { + for (const entityKeyType of Object.keys(alarmData.latest)) { + const keyTypeValues = alarmData.latest[entityKeyType]; + for (const key of Object.keys(keyTypeValues)) { + const tsValue = keyTypeValues[key]; + if (tsValue.ts) { + tsValue.ts += tsOffset; + } + if (key in [entityFields.createdTime.keyName, + alarmFields.startTime.keyName, + alarmFields.endTime.keyName, + alarmFields.ackTime.keyName, + alarmFields.clearTime.keyName] && tsValue.value) { + tsValue.value = (Number(tsValue.value) + tsOffset) + ''; + } + } + } + } + } + } } export class EntityCountUpdate extends CmdUpdate { @@ -466,7 +549,7 @@ export class TelemetrySubscriber { private entityCountSubject = new ReplaySubject(1); private reconnectSubject = new Subject(); - private zone: NgZone; + private tsOffset = undefined; public subscriptionCommands: Array; @@ -491,13 +574,12 @@ export class TelemetrySubscriber { if (keys) { subscriptionCommand.keys = keys.join(','); } - const subscriber = new TelemetrySubscriber(telemetryService); - subscriber.zone = zone; + const subscriber = new TelemetrySubscriber(telemetryService, zone); subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } - constructor(private telemetryService: TelemetryService) { + constructor(private telemetryService: TelemetryService, private zone?: NgZone) { this.subscriptionCommands = []; } @@ -522,6 +604,16 @@ export class TelemetrySubscriber { this.reconnectSubject.complete(); } + public setTsOffset(tsOffset: number): boolean { + if (this.tsOffset !== tsOffset) { + const changed = !isUndefined(this.tsOffset); + this.tsOffset = tsOffset; + return changed; + } else { + return false; + } + } + public onData(message: SubscriptionUpdate) { const cmdId = message.subscriptionId; let keys: string[]; @@ -545,6 +637,9 @@ export class TelemetrySubscriber { } public onEntityData(message: EntityDataUpdate) { + if (this.tsOffset) { + message.prepareData(this.tsOffset); + } if (this.zone) { this.zone.run( () => { @@ -557,6 +652,9 @@ export class TelemetrySubscriber { } public onAlarmData(message: AlarmDataUpdate) { + if (this.tsOffset) { + message.prepareData(this.tsOffset); + } if (this.zone) { this.zone.run( () => { diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 1156840915..c9520f09df 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -17,9 +17,7 @@ import { TimeService } from '@core/services/time.service'; import { deepClone, isDefined, isUndefined } from '@app/core/utils'; import * as moment_ from 'moment'; -import { Observable } from 'rxjs/internal/Observable'; -import { from, of } from 'rxjs'; -import { map, mergeMap, tap } from 'rxjs/operators'; +import * as momentTz from 'moment-timezone'; const moment = moment_; @@ -27,21 +25,35 @@ export const SECOND = 1000; export const MINUTE = 60 * SECOND; export const HOUR = 60 * MINUTE; export const DAY = 24 * HOUR; +export const WEEK = 7 * DAY; export const YEAR = DAY * 365; +export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval'; + export enum TimewindowType { REALTIME, HISTORY } +export enum RealtimeWindowType { + LAST_INTERVAL, + INTERVAL +} + export enum HistoryWindowType { LAST_INTERVAL, - FIXED + FIXED, + INTERVAL } export interface IntervalWindow { interval?: number; timewindowMs?: number; + quickInterval?: QuickTimeInterval; +} + +export interface RealtimeWindow extends IntervalWindow{ + realtimeType?: RealtimeWindowType; } export interface FixedWindow { @@ -82,13 +94,16 @@ export interface Aggregation { export interface Timewindow { displayValue?: string; + displayTimezoneAbbr?: string; hideInterval?: boolean; hideAggregation?: boolean; hideAggInterval?: boolean; + hideTimezone?: boolean; selectedTab?: TimewindowType; - realtime?: IntervalWindow; + realtime?: RealtimeWindow; history?: HistoryWindow; aggregation?: Aggregation; + timezone?: string; } export interface SubscriptionAggregation extends Aggregation { @@ -99,40 +114,89 @@ export interface SubscriptionAggregation extends Aggregation { export interface SubscriptionTimewindow { startTs?: number; + quickInterval?: QuickTimeInterval; + timezone?: string; + tsOffset?: number; realtimeWindowMs?: number; fixedWindow?: FixedWindow; aggregation?: SubscriptionAggregation; + timeForComparison?: ComparisonDuration; } export interface WidgetTimewindow { minTime?: number; maxTime?: number; interval?: number; + timezone?: string; stDiff?: number; } +export enum QuickTimeInterval { + YESTERDAY = 'YESTERDAY', + DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', + THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', + PREVIOUS_WEEK = 'PREVIOUS_WEEK', + PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO', + PREVIOUS_MONTH = 'PREVIOUS_MONTH', + PREVIOUS_YEAR = 'PREVIOUS_YEAR', + CURRENT_HOUR = 'CURRENT_HOUR', + CURRENT_DAY = 'CURRENT_DAY', + CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR', + CURRENT_WEEK = 'CURRENT_WEEK', + CURRENT_WEEK_ISO = 'CURRENT_WEEK_ISO', + CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_FAR', + CURRENT_WEEK_ISO_SO_FAR = 'CURRENT_WEEK_ISO_SO_FAR', + CURRENT_MONTH = 'CURRENT_MONTH', + CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', + CURRENT_YEAR = 'CURRENT_YEAR', + CURRENT_YEAR_SO_FAR = 'CURRENT_YEAR_SO_FAR' +} + +export const QuickTimeIntervalTranslationMap = new Map([ + [QuickTimeInterval.YESTERDAY, 'timeinterval.predefined.yesterday'], + [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], + [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], + [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], + [QuickTimeInterval.PREVIOUS_WEEK_ISO, 'timeinterval.predefined.previous-week-iso'], + [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], + [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], + [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'], + [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'], + [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'], + [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], + [QuickTimeInterval.CURRENT_WEEK_ISO, 'timeinterval.predefined.current-week-iso'], + [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'], + [QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR, 'timeinterval.predefined.current-week-iso-so-far'], + [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], + [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], + [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], + [QuickTimeInterval.CURRENT_YEAR_SO_FAR, 'timeinterval.predefined.current-year-so-far'] +]); + export function historyInterval(timewindowMs: number): Timewindow { - const timewindow: Timewindow = { + return { selectedTab: TimewindowType.HISTORY, history: { historyType: HistoryWindowType.LAST_INTERVAL, timewindowMs } }; - return timewindow; } export function defaultTimewindow(timeService: TimeService): Timewindow { const currentTime = moment().valueOf(); - const timewindow: Timewindow = { + return { displayValue: '', hideInterval: false, hideAggregation: false, hideAggInterval: false, + hideTimezone: false, selectedTab: TimewindowType.REALTIME, realtime: { + realtimeType: RealtimeWindowType.LAST_INTERVAL, interval: SECOND, - timewindowMs: MINUTE + timewindowMs: MINUTE, + quickInterval: QuickTimeInterval.CURRENT_DAY }, history: { historyType: HistoryWindowType.LAST_INTERVAL, @@ -141,14 +205,22 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { fixedTimewindow: { startTimeMs: currentTime - DAY, endTimeMs: currentTime - } + }, + quickInterval: QuickTimeInterval.CURRENT_DAY }, aggregation: { type: AggregationType.AVG, limit: Math.floor(timeService.getMaxDatapointsLimit() / 2) } }; - return timewindow; +} + +function getTimewindowType(timewindow: Timewindow): TimewindowType { + if (isUndefined(timewindow.selectedTab)) { + return isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY; + } else { + return timewindow.selectedTab; + } } export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { @@ -157,20 +229,26 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T model.hideInterval = value.hideInterval; model.hideAggregation = value.hideAggregation; model.hideAggInterval = value.hideAggInterval; - if (isUndefined(value.selectedTab)) { - if (value.realtime) { - model.selectedTab = TimewindowType.REALTIME; - } else { - model.selectedTab = TimewindowType.HISTORY; - } - } else { - model.selectedTab = value.selectedTab; - } + model.hideTimezone = value.hideTimezone; + model.selectedTab = getTimewindowType(value); if (model.selectedTab === TimewindowType.REALTIME) { if (isDefined(value.realtime.interval)) { model.realtime.interval = value.realtime.interval; } - model.realtime.timewindowMs = value.realtime.timewindowMs; + if (isUndefined(value.realtime.realtimeType)) { + if (isDefined(value.realtime.quickInterval)) { + model.realtime.realtimeType = RealtimeWindowType.INTERVAL; + } else { + model.realtime.realtimeType = RealtimeWindowType.LAST_INTERVAL; + } + } else { + model.realtime.realtimeType = value.realtime.realtimeType; + } + if (model.realtime.realtimeType === RealtimeWindowType.INTERVAL) { + model.realtime.quickInterval = value.realtime.quickInterval; + } else { + model.realtime.timewindowMs = value.realtime.timewindowMs; + } } else { if (isDefined(value.history.interval)) { model.history.interval = value.history.interval; @@ -178,6 +256,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T if (isUndefined(value.history.historyType)) { if (isDefined(value.history.timewindowMs)) { model.history.historyType = HistoryWindowType.LAST_INTERVAL; + } else if (isDefined(value.history.quickInterval)) { + model.history.historyType = HistoryWindowType.INTERVAL; } else { model.history.historyType = HistoryWindowType.FIXED; } @@ -186,6 +266,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T } if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) { model.history.timewindowMs = value.history.timewindowMs; + } else if (model.history.historyType === HistoryWindowType.INTERVAL) { + model.history.quickInterval = value.history.quickInterval; } else { model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; @@ -197,6 +279,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T } model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); } + model.timezone = value.timezone; } return model; } @@ -219,10 +302,11 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, aggType = AggregationType.AVG; limit = timeService.getMaxDatapointsLimit(); } - const historyTimewindow: Timewindow = { + return { hideInterval: timewindow.hideInterval || false, hideAggregation: timewindow.hideAggregation || false, hideAggInterval: timewindow.hideAggInterval || false, + hideTimezone: timewindow.hideTimezone || false, selectedTab: TimewindowType.HISTORY, history: { historyType: HistoryWindowType.FIXED, @@ -235,9 +319,32 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, aggregation: { type: aggType, limit - } + }, + timezone: timewindow.timezone }; - return historyTimewindow; +} + +export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean { + if (!newTimewindow || !oldTimewindow) { + return false; + } + const newType = getTimewindowType(newTimewindow); + const oldType = getTimewindowType(oldTimewindow); + return newType !== oldType; +} + +export function calculateTsOffset(timezone?: string): number { + if (timezone) { + const tz = getTimezone(timezone); + const localOffset = moment().utcOffset(); + return (tz.utcOffset() - localOffset) * 60 * 1000; + } else { + return 0; + } +} + +export function isHistoryTypeTimewindow(timewindow: Timewindow): boolean { + return getTimewindowType(timewindow) === TimewindowType.HISTORY; } export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, @@ -249,9 +356,11 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num interval: SECOND, limit: timeService.getMaxDatapointsLimit(), type: AggregationType.AVG - } + }, + timezone: timewindow.timezone, + tsOffset: calculateTsOffset(timewindow.timezone) }; - let aggTimewindow = 0; + let aggTimewindow; if (stateData) { subscriptionTimewindow.aggregation.type = AggregationType.NONE; subscriptionTimewindow.aggregation.stateData = true; @@ -262,38 +371,69 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit() }; } - let selectedTab = timewindow.selectedTab; - if (isUndefined(selectedTab)) { - selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY; - } + const selectedTab = getTimewindowType(timewindow); if (selectedTab === TimewindowType.REALTIME) { - subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; + let realtimeType = timewindow.realtime.realtimeType; + if (isUndefined(realtimeType)) { + if (isDefined(timewindow.realtime.quickInterval)) { + realtimeType = RealtimeWindowType.INTERVAL; + } else { + realtimeType = RealtimeWindowType.LAST_INTERVAL; + } + } + if (realtimeType === RealtimeWindowType.INTERVAL) { + subscriptionTimewindow.realtimeWindowMs = + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, timewindow.timezone); + subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; + const currentDate = getCurrentTime(timewindow.timezone); + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate).valueOf(); + } else { + subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; + const currentDate = getCurrentTime(timewindow.timezone); + subscriptionTimewindow.startTs = currentDate.valueOf() + stDiff - subscriptionTimewindow.realtimeWindowMs; + } subscriptionTimewindow.aggregation.interval = timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, subscriptionTimewindow.aggregation.type); - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; - const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; aggTimewindow = subscriptionTimewindow.realtimeWindowMs; - if (startDiff) { - subscriptionTimewindow.startTs -= startDiff; - aggTimewindow += subscriptionTimewindow.aggregation.interval; + if (realtimeType !== RealtimeWindowType.INTERVAL) { + const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; + if (startDiff) { + subscriptionTimewindow.startTs -= startDiff; + aggTimewindow += subscriptionTimewindow.aggregation.interval; + } } } else { let historyType = timewindow.history.historyType; if (isUndefined(historyType)) { - historyType = isDefined(timewindow.history.timewindowMs) ? HistoryWindowType.LAST_INTERVAL : HistoryWindowType.FIXED; + if (isDefined(timewindow.history.timewindowMs)) { + historyType = HistoryWindowType.LAST_INTERVAL; + } else if (isDefined(timewindow.history.quickInterval)) { + historyType = HistoryWindowType.INTERVAL; + } else { + historyType = HistoryWindowType.FIXED; + } } if (historyType === HistoryWindowType.LAST_INTERVAL) { - const currentTime = Date.now(); + const currentDate = getCurrentTime(timewindow.timezone); + const currentTime = currentDate.valueOf(); subscriptionTimewindow.fixedWindow = { startTimeMs: currentTime - timewindow.history.timewindowMs, endTimeMs: currentTime }; aggTimewindow = timewindow.history.timewindowMs; + } else if (historyType === HistoryWindowType.INTERVAL) { + const startEndTime = calculateIntervalStartEndTime(timewindow.history.quickInterval, timewindow.timezone); + subscriptionTimewindow.fixedWindow = { + startTimeMs: startEndTime[0], + endTimeMs: startEndTime[1] + }; + aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; } else { subscriptionTimewindow.fixedWindow = { - startTimeMs: timewindow.history.fixedTimewindow.startTimeMs, - endTimeMs: timewindow.history.fixedTimewindow.endTimeMs + startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset, + endTimeMs: timewindow.history.fixedTimewindow.endTimeMs - subscriptionTimewindow.tsOffset }; aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; } @@ -309,22 +449,230 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num return subscriptionTimewindow; } +function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, tz?: string): number { + let currentDate; + switch (interval) { + case QuickTimeInterval.CURRENT_HOUR: + return HOUR; + case QuickTimeInterval.CURRENT_DAY: + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + return DAY; + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + return WEEK; + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + currentDate = getCurrentTime(tz); + return currentDate.endOf('month').diff(currentDate.clone().startOf('month')); + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + currentDate = getCurrentTime(tz); + return currentDate.endOf('year').diff(currentDate.clone().startOf('year')); + } +} + +export function calculateIntervalStartEndTime(interval: QuickTimeInterval, tz?: string): [number, number] { + const startEndTs: [number, number] = [0, 0]; + const currentDate = getCurrentTime(tz); + const startDate = calculateIntervalStartTime(interval, currentDate); + startEndTs[0] = startDate.valueOf(); + const endDate = calculateIntervalEndTime(interval, startDate, tz); + startEndTs[1] = endDate.valueOf(); + return startEndTs; +} + +export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment): moment_.Moment { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + currentDate.subtract(1, 'days'); + return currentDate.startOf('day'); + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + currentDate.subtract(2, 'days'); + return currentDate.startOf('day'); + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + currentDate.subtract(1, 'weeks'); + return currentDate.startOf('day'); + case QuickTimeInterval.PREVIOUS_WEEK: + currentDate.subtract(1, 'weeks'); + return currentDate.startOf('week'); + case QuickTimeInterval.PREVIOUS_WEEK_ISO: + currentDate.subtract(1, 'weeks'); + return currentDate.startOf('isoWeek'); + case QuickTimeInterval.PREVIOUS_MONTH: + currentDate.subtract(1, 'months'); + return currentDate.startOf('month'); + case QuickTimeInterval.PREVIOUS_YEAR: + currentDate.subtract(1, 'years'); + return currentDate.startOf('year'); + case QuickTimeInterval.CURRENT_HOUR: + return currentDate.startOf('hour'); + case QuickTimeInterval.CURRENT_DAY: + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + return currentDate.startOf('day'); + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + return currentDate.startOf('week'); + case QuickTimeInterval.CURRENT_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + return currentDate.startOf('isoWeek'); + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + return currentDate.startOf('month'); + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return currentDate.startOf('year'); + } +} + +export function calculateIntervalEndTime(interval: QuickTimeInterval, startDate: moment_.Moment, tz?: string): number { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + case QuickTimeInterval.CURRENT_DAY: + return startDate.add(1, 'day').valueOf(); + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.PREVIOUS_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_ISO: + return startDate.add(1, 'week').valueOf(); + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + return startDate.add(1, 'month').valueOf(); + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + return startDate.add(1, 'year').valueOf(); + case QuickTimeInterval.CURRENT_HOUR: + return startDate.add(1, 'hour').valueOf(); + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return getCurrentTime(tz).valueOf(); + } +} + +export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { + switch (interval) { + case QuickTimeInterval.CURRENT_HOUR: + return HOUR; + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + case QuickTimeInterval.CURRENT_DAY: + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + return DAY; + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.PREVIOUS_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + return WEEK; + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + return DAY * 30; + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return YEAR; + } +} + +export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval, + startDate: moment_.Moment): moment_.Moment { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.CURRENT_DAY: + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + startDate.subtract(1, 'days'); + return startDate.startOf('day'); + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + startDate.subtract(1, 'weeks'); + return startDate.startOf('day'); + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + startDate.subtract(1, 'weeks'); + return startDate.startOf('week'); + case QuickTimeInterval.PREVIOUS_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK_ISO: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + startDate.subtract(1, 'weeks'); + return startDate.startOf('isoWeek'); + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + startDate.subtract(1, 'months'); + return startDate.startOf('month'); + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + startDate.subtract(1, 'years'); + return startDate.startOf('year'); + case QuickTimeInterval.CURRENT_HOUR: + startDate.subtract(1, 'hour'); + return startDate.startOf('hour'); + } +} + +export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval, + comparisonStartDate: moment_.Moment, + endDate: moment_.Moment): number { + switch (interval) { + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + return endDate.subtract(1, 'days').valueOf(); + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: + return endDate.subtract(1, 'week').valueOf(); + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + return endDate.subtract(1, 'month').valueOf(); + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return endDate.subtract(1, 'year').valueOf(); + default: + return calculateIntervalEndTime(interval, comparisonStartDate); + } +} + export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { + timeUnit: ComparisonDuration): SubscriptionTimewindow { const timewindowForComparison: SubscriptionTimewindow = { fixedWindow: null, realtimeWindowMs: null, - aggregation: subscriptionTimewindow.aggregation + aggregation: subscriptionTimewindow.aggregation, + tsOffset: subscriptionTimewindow.tsOffset }; - if (subscriptionTimewindow.realtimeWindowMs) { - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf(); - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs; - } else if (subscriptionTimewindow.fixedWindow) { - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); - - timewindowForComparison.startTs = endTimeMs - timeInterval; + if (subscriptionTimewindow.fixedWindow) { + let startTimeMs; + let endTimeMs; + if (timeUnit === 'previousInterval') { + if (subscriptionTimewindow.quickInterval) { + const startDate = moment(subscriptionTimewindow.fixedWindow.startTimeMs); + const endDate = moment(subscriptionTimewindow.fixedWindow.endTimeMs); + if (subscriptionTimewindow.timezone) { + startDate.tz(subscriptionTimewindow.timezone); + endDate.tz(subscriptionTimewindow.timezone); + } + const comparisonStartDate = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate); + startTimeMs = comparisonStartDate.valueOf(); + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, comparisonStartDate, endDate); + } else { + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; + startTimeMs = endTimeMs - timeInterval; + } + } else { + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); + startTimeMs = endTimeMs - timeInterval; + } + timewindowForComparison.startTs = startTimeMs; timewindowForComparison.fixedWindow = { startTimeMs: timewindowForComparison.startTs, endTimeMs @@ -339,6 +687,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { cloned.hideInterval = timewindow.hideInterval || false; cloned.hideAggregation = timewindow.hideAggregation || false; cloned.hideAggInterval = timewindow.hideAggInterval || false; + cloned.hideTimezone = timewindow.hideTimezone || false; if (isDefined(timewindow.selectedTab)) { cloned.selectedTab = timewindow.selectedTab; if (timewindow.selectedTab === TimewindowType.REALTIME) { @@ -348,20 +697,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { } } cloned.aggregation = deepClone(timewindow.aggregation); - return cloned; -} - -export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): HistoryWindow { - const cloned: HistoryWindow = {}; - if (isDefined(historyWindow.historyType)) { - cloned.historyType = historyWindow.historyType; - cloned.interval = historyWindow.interval; - if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) { - cloned.timewindowMs = historyWindow.timewindowMs; - } else if (historyWindow.historyType === HistoryWindowType.FIXED) { - cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow); - } - } + cloned.timezone = timewindow.timezone; return cloned; } @@ -375,7 +711,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.seconds-interval', translateParams: {seconds: 1}, - value: 1 * SECOND + value: SECOND }, { name: 'timeinterval.seconds-interval', @@ -400,7 +736,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.minutes-interval', translateParams: {minutes: 1}, - value: 1 * MINUTE + value: MINUTE }, { name: 'timeinterval.minutes-interval', @@ -430,7 +766,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.hours-interval', translateParams: {hours: 1}, - value: 1 * HOUR + value: HOUR }, { name: 'timeinterval.hours-interval', @@ -455,7 +791,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.days-interval', translateParams: {days: 1}, - value: 1 * DAY + value: DAY }, { name: 'timeinterval.days-interval', @@ -490,65 +826,74 @@ export interface TimezoneInfo { name: string; offset: string; nOffset: number; + abbr: string; } let timezones: TimezoneInfo[] = null; let defaultTimezone: string = null; -export function getTimezones(): Observable { - if (timezones) { - return of(timezones); - } else { - return from(import('moment-timezone')).pipe( - map((monentTz) => { - return monentTz.tz.names().map((zoneName) => { - const tz = monentTz.tz(zoneName); - return { - id: zoneName, - name: zoneName.replace(/_/g, ' '), - offset: `UTC${tz.format('Z')}`, - nOffset: tz.utcOffset() - }; - }); - }), - tap((zones) => { - timezones = zones; - }) - ); +export function getTimezones(): TimezoneInfo[] { + if (!timezones) { + timezones = momentTz.tz.names().map((zoneName) => { + const tz = momentTz.tz(zoneName); + return { + id: zoneName, + name: zoneName.replace(/_/g, ' '), + offset: `UTC${tz.format('Z')}`, + nOffset: tz.utcOffset(), + abbr: tz.zoneAbbr() + }; + }); } + return timezones; } -export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable { - return getTimezones().pipe( - mergeMap((timezoneList) => { - let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId); - if (!foundTimezone) { - if (userTimezoneByDefault) { - return getDefaultTimezone().pipe( - map((userTimezone) => { - return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone); - }) - ); - } else if (defaultTimezoneId) { - foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId); - } - } - return of(foundTimezone); - }) - ); +export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): TimezoneInfo { + const timezoneList = getTimezones(); + let foundTimezone = timezoneId ? timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId) : null; + if (!foundTimezone) { + if (userTimezoneByDefault) { + const userTimezone = getDefaultTimezone(); + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone); + } else if (defaultTimezoneId) { + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId); + } + } + return foundTimezone; } -export function getDefaultTimezone(): Observable { - if (defaultTimezone) { - return of(defaultTimezone); +export function getDefaultTimezoneInfo(): TimezoneInfo { + const userTimezone = getDefaultTimezone(); + return getTimezoneInfo(userTimezone); +} + +export function getDefaultTimezone(): string { + if (!defaultTimezone) { + defaultTimezone = momentTz.tz.guess(); + } + return defaultTimezone; +} + +export function getCurrentTime(tz?: string): moment_.Moment { + if (tz) { + return moment().tz(tz); + } else { + return moment(); + } +} + +export function getTime(ts: number, tz?: string): moment_.Moment { + if (tz) { + return moment(ts).tz(tz); } else { - return from(import('moment-timezone')).pipe( - map((monentTz) => { - return monentTz.tz.guess(); - }), - tap((zone) => { - defaultTimezone = zone; - }) - ); + return moment(ts); } } + +export function getTimezone(tz: string): moment_.Moment { + return moment.tz(tz); +} + +export function getCurrentTimeForComparison(timeForComparison: moment_.unitOfTime.DurationConstructor, tz?: string): moment_.Moment { + return getCurrentTime(tz).subtract(1, timeForComparison); +} diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 33b12045c6..2d599bdc78 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -63,7 +63,7 @@ export const widgetTypesData = new Map( [ widgetType.latest, { - name: 'widget.latest-values', + name: 'widget.latest', icon: 'track_changes', configHelpLinkId: 'widgetsConfigLatest', template: { @@ -165,14 +165,28 @@ export interface WidgetControllerDescriptor { actionSources?: {[actionSourceId: string]: WidgetActionSource}; } -export interface WidgetType extends BaseData { +export interface BaseWidgetType extends BaseData { tenantId: TenantId; bundleAlias: string; alias: string; name: string; +} + +export interface WidgetType extends BaseWidgetType { descriptor: WidgetTypeDescriptor; } +export interface WidgetTypeInfo extends BaseWidgetType { + image: string; + description: string; + widgetType: widgetType; +} + +export interface WidgetTypeDetails extends WidgetType { + image: string; + description: string; +} + export enum LegendDirection { column = 'column', row = 'row' @@ -399,14 +413,8 @@ export interface WidgetConfig { [key: string]: any; } -export interface Widget { - id?: string; +export interface Widget extends WidgetInfo{ typeId?: WidgetTypeId; - isSystemType: boolean; - bundleAlias: string; - typeAlias: string; - type: widgetType; - title: string; sizeX: number; sizeY: number; row: number; @@ -414,6 +422,17 @@ export interface Widget { config: WidgetConfig; } +export interface WidgetInfo { + id?: string; + isSystemType: boolean; + bundleAlias: string; + typeAlias: string; + type: widgetType; + title: string; + image?: string; + description?: string; +} + export interface GroupInfo { formIndex: number; GroupTitle: string; @@ -429,7 +448,7 @@ export interface JsonSchema { export interface JsonSettingsSchema { schema?: JsonSchema; form?: any[]; - groupInfoes?: GroupInfo[] + groupInfoes?: GroupInfo[]; } export interface WidgetPosition { diff --git a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts index cb25521087..6a4db3f80d 100644 --- a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts +++ b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts @@ -23,4 +23,5 @@ export interface WidgetsBundle extends BaseData { alias: string; title: string; image: string; + description: string; } diff --git a/ui-ngx/src/app/shared/pipe/file-size.pipe.ts b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts new file mode 100644 index 0000000000..6d43d42fce --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Pipe, PipeTransform } from '@angular/core'; + +type unit = 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB'; +type unitPrecisionMap = { + [u in unit]: number; +}; + +const defaultPrecisionMap: unitPrecisionMap = { + bytes: 0, + KB: 0, + MB: 1, + GB: 1, + TB: 2, + PB: 2 +}; + +@Pipe({ name: 'fileSize' }) +export class FileSizePipe implements PipeTransform { + private readonly units: unit[] = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + + transform(bytes: number = 0, precision: number | unitPrecisionMap = defaultPrecisionMap): string { + if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) { + return '?'; + } + + let unitIndex = 0; + + while (bytes >= 1024) { + bytes /= 1024; + unitIndex++; + } + + const unitSymbol = this.units[unitIndex]; + + if (typeof precision === 'number') { + return `${bytes.toFixed(+precision)} ${unitSymbol}`; + } + return `${bytes.toFixed(precision[unitSymbol])} ${unitSymbol}`; + } +} diff --git a/ui-ngx/src/app/shared/pipe/public-api.ts b/ui-ngx/src/app/shared/pipe/public-api.ts index 0a869fcc52..07543f39fd 100644 --- a/ui-ngx/src/app/shared/pipe/public-api.ts +++ b/ui-ngx/src/app/shared/pipe/public-api.ts @@ -20,3 +20,5 @@ export * from './keyboard-shortcut.pipe'; export * from './milliseconds-to-time-string.pipe'; export * from './nospace.pipe'; export * from './truncate.pipe'; +export * from './file-size.pipe'; +export * from './selectable-columns.pipe'; diff --git a/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts new file mode 100644 index 0000000000..acfa00693f --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts @@ -0,0 +1,25 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Pipe, PipeTransform } from '@angular/core'; +import { DisplayColumn } from '@home/components/widget/lib/table-widget.models'; + +@Pipe({ name: 'selectableColumns' }) +export class SelectableColumnsPipe implements PipeTransform { + transform(allColumns: DisplayColumn[]): DisplayColumn[] { + return allColumns.filter(column => column.selectable); + } +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index a142a48e90..02cd3b93d4 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -137,6 +137,10 @@ import { DndModule } from 'ngx-drag-drop'; import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component'; import { ContactComponent } from '@shared/components/contact.component'; import { TimezoneSelectComponent } from '@shared/components/time/timezone-select.component'; +import { FileSizePipe } from '@shared/pipe/file-size.pipe'; +import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; +import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; +import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component'; @NgModule({ providers: [ @@ -146,6 +150,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select HighlightPipe, TruncatePipe, TbJsonPipe, + FileSizePipe, { provide: FlowInjectionToken, useValue: Flow @@ -171,6 +176,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + QuickTimeIntervalComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -219,12 +225,15 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select HighlightPipe, TruncatePipe, TbJsonPipe, + FileSizePipe, + SelectableColumnsPipe, KeyboardShortcutPipe, TbJsonToStringDirective, JsonObjectEditDialogComponent, HistorySelectorComponent, EntityGatewaySelectComponent, - ContactComponent + ContactComponent, + WidgetsBundleSearchComponent ], imports: [ CommonModule, @@ -295,6 +304,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + QuickTimeIntervalComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, @@ -387,11 +397,14 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select TruncatePipe, TbJsonPipe, KeyboardShortcutPipe, + FileSizePipe, + SelectableColumnsPipe, TranslateModule, JsonObjectEditDialogComponent, HistorySelectorComponent, EntityGatewaySelectComponent, - ContactComponent + ContactComponent, + WidgetsBundleSearchComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 28fb3a4b48..888154e3bf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -2206,7 +2206,7 @@ "timeseries": "Časové řady", "search-data": "Vyhledat data", "no-data-found": "Žádná data nebyla nalezena", - "latest-values": "Poslední hodnoty", + "latest": "Poslední hodnoty", "rpc": "Ovládací widget", "alarm": "Widgety alarmu", "static": "Statické widgety", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 289bbaa0be..712ad0fa2d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -1667,7 +1667,7 @@ "timeseries": "Zeitreihe", "search-data": "Daten suchen", "no-data-found": "Keine Daten gefunden", - "latest-values": "Neueste Werte", + "latest": "Neueste Werte", "rpc": "Steuerungswidget", "alarm": "Alarm-Widget", "static": "Statisches Widget", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index b65a875aed..2d2ab96de5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -2316,7 +2316,7 @@ "timeseries": "Χρονική σειρά", "search-data": "Αναζήτηση δεδομένων", "no-data-found": "Δεν βρέθηκαν δεδομένα", - "latest-values": "Τελευταίες αξίες", + "latest": "Τελευταίες αξίες", "rpc": "Έλεγχος Widget", "alarm": "Alarm widget", "static": "Στατικό widget", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 86b503823f..5ca29d0af4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -667,6 +667,7 @@ "add-widget": "Add new widget", "title": "Title", "select-widget-title": "Select widget", + "select-widget-value": "{{title}}: select widget", "select-widget-subtitle": "List of available widget types", "delete": "Delete dashboard", "title-required": "Title is required.", @@ -712,6 +713,8 @@ "background-size-mode": "Background size mode", "no-image": "No image selected", "drop-image": "Drop an image or click to select a file to upload.", + "maximum-upload-file-size": "Maximum upload file size: {{ size }}", + "cannot-upload-file": "Cannot upload file", "settings": "Settings", "columns-count": "Columns count", "columns-count-required": "Columns count is required.", @@ -1022,6 +1025,8 @@ "transport-type-mqtt-hint": "Enables advanced MQTT transport settings", "transport-type-lwm2m": "LWM2M", "transport-type-lwm2m-hint": "LWM2M transport type", + "transport-type-coap": "CoAP", + "transport-type-coap-hint": "Enables advanced CoAP transport settings", "description": "Description", "default": "Default", "profile-configuration": "Profile configuration", @@ -1039,9 +1044,14 @@ "mqtt-device-topic-filters": "MQTT device topic filters", "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.", "mqtt-device-payload-type": "MQTT device payload", - "mqtt-device-payload-type-json": "JSON", - "mqtt-device-payload-type-proto": "Protobuf", + "transport-device-payload-type-json": "JSON", + "transport-device-payload-type-proto": "Protobuf", "mqtt-payload-type-required": "Payload type is required.", + "coap-device-type": "CoAP device type", + "coap-device-payload-type": "CoAP device payload", + "coap-device-type-required": "CoAP device type is required.", + "coap-device-type-default": "Default", + "coap-device-type-efento": "Efento NB-IoT", "support-level-wildcards": "Single [+] and multi-level [#] wildcards supported.", "telemetry-topic-filter": "Telemetry topic filter", "telemetry-topic-filter-required": "Telemetry topic filter is required.", @@ -1189,7 +1199,13 @@ "bootstrap-server-account-timeout": "Account after the timeout", "bootstrap-server-account-timeout-tip": "Bootstrap-Server Account after the timeout value given by this resource.", "config-json-tab": "Json Config Profile Device" - } + }, + "create-device-profile": "Create new device profile", + "import": "Import device profile", + "export": "Export device profile", + "export-failed-error": "Unable to export device profile: {{error}}", + "device-profile-file": "Device profile file", + "invalid-device-profile-file-error": "Unable to import device profile: Invalid device profile data structure." }, "dialog": { "close": "Close dialog" @@ -2049,6 +2065,7 @@ "avg": "avg", "total": "total", "comparison-time-ago": { + "previousInterval": "(previous interval)", "days": "(day ago)", "weeks": "(week ago)", "months": "(month ago)", @@ -2260,7 +2277,8 @@ "timezone": "Timezone", "select-timezone": "Select timezone", "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", - "timezone-required": "Timezone is required." + "timezone-required": "Timezone is required.", + "browser-time": "Browser Time" }, "queue": { "select_name": "Select queue name", @@ -2390,7 +2408,27 @@ "hours": "Hours", "minutes": "Minutes", "seconds": "Seconds", - "advanced": "Advanced" + "advanced": "Advanced", + "predefined": { + "yesterday": "Yesterday", + "day-before-yesterday": "Day before yesterday", + "this-day-last-week": "This day last week", + "previous-week": "Previous week (Sun - Sat)", + "previous-week-iso": "Previous week (Mon - Sun)", + "previous-month": "Previous month", + "previous-year": "Previous year", + "current-hour": "Current hour", + "current-day": "Current day", + "current-day-so-far": "Current day so far", + "current-week": "Current week (Sun - Sat)", + "current-week-iso": "Current week (Mon - Sun)", + "current-week-so-far": "Current week so far (Sun - Sat)", + "current-week-iso-so-far": "Current week so far (Mon - Sun)", + "current-month": "Current month", + "current-month-so-far": "Current month so far", + "current-year": "Current year", + "current-year-so-far": "Current year so far" + } }, "timeunit": { "seconds": "Seconds", @@ -2411,7 +2449,8 @@ "date-range": "Date range", "last": "Last", "time-period": "Time period", - "hide": "Hide" + "hide": "Hide", + "interval": "Interval" }, "user": { "user": "User", @@ -2487,6 +2526,7 @@ "widget": { "widget-library": "Widgets Library", "widget-bundle": "Widgets Bundle", + "all-bundles": "All bundles", "select-widgets-bundle": "Select widgets bundle", "management": "Widget management", "editor": "Widget Editor", @@ -2499,7 +2539,7 @@ "timeseries": "Time series", "search-data": "Search data", "no-data-found": "No data found", - "latest-values": "Latest values", + "latest": "Latest values", "rpc": "Control widget", "alarm": "Alarm widget", "static": "Static widget", @@ -2526,6 +2566,9 @@ "css": "CSS", "settings-schema": "Settings schema", "datakey-settings-schema": "Data key settings schema", + "widget-settings": "Widget settings", + "description": "Description", + "image-preview": "Image preview", "javascript": "Javascript", "js": "JS", "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?", @@ -2539,7 +2582,10 @@ "export": "Export widget", "no-data": "No data to display on widget", "data-overflow": "Widget displays {{count}} out of {{total}} entities", - "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities" + "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities", + "search": "Search widget", + "filter": "Widget filter type", + "loading-widgets": "Loading widgets..." }, "widget-action": { "header-button": "Widget header button", @@ -2568,6 +2614,8 @@ "delete": "Delete widgets bundle", "title": "Title", "title-required": "Title is required.", + "description": "Description", + "image-preview": "Image preview", "add-widgets-bundle-text": "Add new widgets bundle", "no-widgets-bundles-text": "No widgets bundles found", "empty": "Widgets bundle is empty", @@ -2589,7 +2637,8 @@ "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.", "search": "Search widget bundles", "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected", - "open-widgets-bundle": "Open widgets bundle" + "open-widgets-bundle": "Open widgets bundle", + "loading-widgets-bundles": "Loading widgets bundles..." }, "widget-config": { "data": "Data", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 7c4394c026..ba278e796e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -2422,7 +2422,7 @@ "timeseries": "Series de tiempo", "search-data": "Buscar datos", "no-data-found": "No se han encontrado datos", - "latest-values": "Últimos valores", + "latest": "Últimos valores", "rpc": "Widget de control", "alarm": "Widget de Alarma", "static": "Widget estático", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index 05e6aeb2f6..5327f0b33b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -1420,7 +1420,7 @@ "timeseries": "سري هاي زماني", "search-data": "جستجوي داده", "no-data-found": "هيچ داده اي يافت نشد", - "latest-values": "آخرين مقادير", + "latest": "آخرين مقادير", "rpc": "ويجت کنترل", "alarm": "ويجت هشدار", "static": "ويجت ايستا", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 074042d65b..a5c25909b0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1705,7 +1705,7 @@ "export": "Exporter widget", "html": "HTML", "javascript": "Javascript", - "latest-values": "Dernières valeurs", + "latest": "Dernières valeurs", "management": "Gestion des widgets", "missing-widget-title-error": "Le titre du widget doit être spécifié!", "no-data-found": "Aucune donnée trouvée", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 9313471fa5..363801ebc3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -1463,7 +1463,7 @@ "timeseries": "Time series", "search-data": "Cerca dati", "no-data-found": "Nessun dato trovato", - "latest-values": "Ultimi valori", + "latest": "Ultimi valori", "rpc": "Control widget", "alarm": "Alarm widget", "static": "Static widget", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json index 980daadaa6..88d666423c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json @@ -1305,7 +1305,7 @@ "timeseries": "時系列", "search-data": "検索データ", "no-data-found": "何もデータが見つかりませんでした", - "latest-values": "最新の値", + "latest": "最新の値", "rpc": "コントロールウィジェット", "alarm": "アラームウィジェット", "static": "静的ウィジェット", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index d9713d3419..06bde1bb02 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -1551,7 +1551,7 @@ "timeseries": "მონაცემთა სერია", "search-data": "საძიებო მონაცემები", "no-data-found": "მონაცემი ვერ მოიძებნა", - "latest-values": "უახლესი მნიშვნელობები", + "latest": "უახლესი მნიშვნელობები", "rpc": "ვიჯეტის კონტროლი", "alarm": "ვიჯეტის განგაში", "static": "სტატიკური ვიჯეტი", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index 3d6a89848b..f52c94a0f0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -2202,7 +2202,7 @@ "timeseries": "시계열", "search-data": "데이터 검색", "no-data-found": "아무 데이터도 없습니다", - "latest-values": "최근 값", + "latest": "최근 값", "rpc": "컨트롤 위젯", "alarm": "알람 위젯", "static": "상태 위젯", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index 4cefaf52fa..76282308e7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -1468,7 +1468,7 @@ "timeseries": "Laika sērijas", "search-data": "Meklēt datus", "no-data-found": "Nav datu atrasti", - "latest-values": "Pedējās vērtības", + "latest": "Pedējās vērtības", "rpc": "Kontroles logrīks", "alarm": "Trauksmes logrīks", "static": "Statiskais logrīks", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index f08a38e9d9..325229b47b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -1777,7 +1777,7 @@ "timeseries": "Intervalos de tempo", "search-data": "Pesquisar dados", "no-data-found": "Nenhum dado encontrado", - "latest-values": "Últimos valores", + "latest": "Últimos valores", "rpc": "Widget de controle", "alarm": "Widget de alarme", "static": "Widget estático", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index 93326e512a..971ad02d00 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -1535,7 +1535,7 @@ "timeseries": "Serii Temporale", "search-data": "Caută Date", "no-data-found": "Nu Au Fost Găsite Date", - "latest-values": "Ultimele Valori", + "latest": "Ultimele Valori", "rpc": "Widget Control ", "alarm": "Widget Alarmă", "static": "Widget Static", diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index 5e0dca82aa..a78416a201 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -1541,7 +1541,7 @@ "timeseries": "Телеметрия", "search-data": "Поиск данных", "no-data-found": "Данные не найдено", - "latest-values": "Последние значения", + "latest": "Последние значения", "rpc": "Управляющий виджет", "alarm": "Виджет оповещений", "static": "Статический виджет", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 1b3077a875..4dffbc6be2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -2202,7 +2202,7 @@ "timeseries": "Časovne serije", "search-data": "Iskanje podatkov", "no-data-found": "Podatkov ni mogoče najti", - "latest-values": "Najnovejše vrednosti", + "latest": "Najnovejše vrednosti", "rpc": "Nadzorni pripomoček", "alarm": "Pripomoček za alarm", "static": "Statični pripomoček", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index dadb85d7fd..06d259dace 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -1464,7 +1464,7 @@ "timeseries": "Zaman serisi", "search-data": "Arama verileri", "no-data-found": "Veri bulunamadı", - "latest-values": "Son değerler", + "latest": "Son değerler", "rpc": "Kontrol göstergesi", "alarm": "Alarm göstergesi", "static": "Statik gösterge", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index c33f591272..f9f33d48b9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -2109,7 +2109,7 @@ "timeseries": "Телеметрія", "search-data": "Пошук даних", "no-data-found": "Даних не знайдено", - "latest-values": "Останні значення", + "latest": "Останні значення", "rpc": "Керуючий віджет", "alarm": "Віджет сигнала тривоги", "static": "Статичний віджет", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 7c95423f80..bbd7f7b61d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -2323,7 +2323,7 @@ "html": "HTML", "javascript": "Javascript", "js": "JS", - "latest-values": "最新值", + "latest": "最新值", "management": "管理部件", "missing-widget-title-error": "部件标题必须指定!", "no-data": "小部件上没有要显示的数据", @@ -2500,4 +2500,4 @@ "value": "价值" } } -} \ No newline at end of file +} diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index ed387affce..14f4268724 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -1396,7 +1396,7 @@ "timeseries": "時間序列", "search-data": "搜尋資料", "no-data-found": "沒有找到資料", - "latest-values": "最新值", + "latest": "最新值", "rpc": "控件部件", "alarm": "警告部件", "static": "靜態部件", diff --git a/ui-ngx/src/assets/widget-preview-empty.svg b/ui-ngx/src/assets/widget-preview-empty.svg new file mode 100644 index 0000000000..bf81b76b1c --- /dev/null +++ b/ui-ngx/src/assets/widget-preview-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ff33b05ea3..1abab9ef9f 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -753,6 +753,9 @@ mat-label { &.tb-mat-20 { @include tb-mat-icon-size(20); } + &.tb-mat-28 { + @include tb-mat-icon-size(28); + } &.tb-mat-32 { @include tb-mat-icon-size(32); } @@ -762,6 +765,9 @@ mat-label { } .mat-icon-button { + &.tb-mat-28 { + @include tb-mat-icon-button-size(28); + } &.tb-mat-32 { @include tb-mat-icon-button-size(32); }