diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json
deleted file mode 100644
index f4eb275c3c..0000000000
--- a/application/src/main/data/json/demo/dashboards/gateways.json
+++ /dev/null
@@ -1,1315 +0,0 @@
-{
- "title": "Gateways",
- "image": null,
- "mobileHide": false,
- "mobileOrder": null,
- "configuration": {
- "widgets": {
- "94715984-ae74-76e4-20b7-2f956b01ed80": {
- "type": "latest",
- "sizeX": 24,
- "sizeY": 12,
- "config": {
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 86400000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085270425,
- "endTimeMs": 1694171670425
- },
- "quickInterval": "CURRENT_DAY"
- },
- "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": false,
- "entitiesTitle": "List of gateways",
- "enableSelectColumnDisplay": true,
- "displayEntityLabel": false,
- "entityNameColumnTitle": "Gateway Name"
- },
- "title": "Devices gateway table",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400,
- "padding": "5px 10px 5px 10px"
- },
- "useDashboardTimewindow": false,
- "showLegend": false,
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "active",
- "type": "attribute",
- "label": "Active",
- "color": "#2196f3",
- "settings": {
- "columnWidth": "0px",
- "useCellStyleFunction": true,
- "useCellContentFunction": true,
- "cellContentFunction": "value = '⬤';\nreturn value;",
- "cellStyleFunction": "var color;\nif (value == 'false') {\n color = '#EB5757';\n} else {\n color = '#27AE60';\n}\nreturn {\n color: color,\n fontSize: '18px'\n};"
- },
- "_hash": 0.3646047595211721
- },
- {
- "name": "eventsSent",
- "type": "timeseries",
- "label": "Sent",
- "color": "#4caf50",
- "settings": {
- "columnWidth": "0px",
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.7235710720767985
- },
- {
- "name": "eventsProduced",
- "type": "timeseries",
- "label": "Events",
- "color": "#f44336",
- "settings": {
- "columnWidth": "0px",
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.5085933386303254
- },
- {
- "name": "LOGS",
- "type": "timeseries",
- "label": "Latest log",
- "color": "#ffc107",
- "settings": {
- "columnWidth": "0px",
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.3504240371585048,
- "postFuncBody": "if(value) {\n return value.substring(0, 31) + \"...\";\n} else {\n return '';\n}"
- },
- {
- "name": "RemoteLoggingLevel",
- "type": "attribute",
- "label": "Log level",
- "color": "#607d8b",
- "settings": {
- "columnWidth": "0px",
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.9785994222542516
- }
- ],
- "entityAliasId": "3e0f533a-0db1-3292-184f-06e73535061a"
- }
- ],
- "showTitleIcon": true,
- "titleIcon": "list",
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "List device",
- "widgetStyle": {},
- "displayTimewindow": true,
- "actions": {
- "headerButton": [
- {
- "name": "Add device",
- "icon": "add",
- "type": "customPretty",
- "customHtml": "
\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 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 additionalInfo: {gateway: true},\n name: vm.addDeviceFormGroup.get('deviceName').value,\n type: 'gateway',\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}\n",
- "customResources": [],
- "id": "70837a9d-c3de-a9a7-03c5-dccd14998758"
- }
- ],
- "actionCellButton": [
- {
- "id": "78845501-234e-a452-6819-82b5b776e99f",
- "name": "Configuration",
- "icon": "settings",
- "type": "openDashboardState",
- "targetDashboardStateId": "__entityname__config",
- "openRightLayout": false,
- "setEntityId": true
- },
- {
- "id": "f6ffdba8-e40f-2b8d-851b-f5ecaf18606b",
- "name": "Graphs",
- "icon": "show_chart",
- "type": "openDashboardState",
- "targetDashboardStateId": "__entityname_grafic",
- "setEntityId": true
- },
- {
- "name": "Edit device",
- "icon": "edit",
- "type": "customPretty",
- "customHtml": "\n",
- "customCss": "/*=======================================================================*/\n/*========== There are two examples: for edit and add entity ==========*/\n/*=======================================================================*/\n/*======================== Edit entity example ========================*/\n/*=======================================================================*/\n/*\n.edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n\n.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n display: none;\n} \n*/\n/*========================================================================*/\n/*========================= Add entity example =========================*/\n/*========================================================================*/\n/*\n.add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n*/\n",
- "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\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 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.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 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}\n",
- "customResources": [],
- "id": "242671f3-76c6-6982-7acc-6f12addf0ccc"
- },
- {
- "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": "862ec2b7-fbcf-376e-f85f-b77c07f36efa"
- }
- ],
- "rowClick": [
- {
- "id": "ad5fc7e1-5e60-e056-6940-a75a383466a1",
- "name": "to_entityname__config",
- "icon": "more_horiz",
- "type": "openDashboardState",
- "targetDashboardStateId": "__entityname__config",
- "setEntityId": true,
- "stateEntityParamName": ""
- }
- ]
- }
- },
- "id": "94715984-ae74-76e4-20b7-2f956b01ed80",
- "typeFullFqn": "system.entity_admin_widgets.device_admin_table"
- },
- "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
- "type": "timeseries",
- "sizeX": 14,
- "sizeY": 13,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "LOGS",
- "type": "timeseries",
- "label": "LOGS",
- "color": "#2196f3",
- "settings": {
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.3496649158709739,
- "postFuncBody": "return value.replace(/ - (.*) - \\[/gi, ' - $1 - [');"
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "realtime": {
- "interval": 1000,
- "timewindowMs": 2592000000
- },
- "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": "Debug events (logs)",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": false,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {},
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true
- },
- "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10",
- "typeFullFqn": "system.cards.timeseries_table"
- },
- "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
- "type": "timeseries",
- "sizeX": 17,
- "sizeY": 4,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "opcuaEventsProduced",
- "type": "timeseries",
- "label": "opcuaEventsProduced",
- "color": "#2196f3",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.1477920581839779
- },
- {
- "name": "opcuaEventsSent",
- "type": "timeseries",
- "label": "opcuaEventsSent",
- "color": "#4caf50",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.6500957113784758
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "realtime": {
- "interval": 1000,
- "timewindowMs": 120000
- },
- "aggregation": {
- "type": "NONE",
- "limit": 25000
- },
- "hideInterval": false,
- "hideAggregation": false
- },
- "showTitle": true,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "shadowSize": 4,
- "fontColor": "#545454",
- "fontSize": 10,
- "xaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "yaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "grid": {
- "color": "#545454",
- "tickColor": "#DDDDDD",
- "verticalLines": true,
- "horizontalLines": true,
- "outlineWidth": 1
- },
- "stack": false,
- "tooltipIndividual": false,
- "timeForComparison": "months",
- "xaxisSecond": {
- "axisPosition": "top",
- "showLabels": true
- },
- "showLegend": true,
- "legendConfig": {
- "direction": "column",
- "position": "right",
- "showMin": true,
- "showMax": true,
- "showAvg": true,
- "showTotal": true
- }
- },
- "title": "Real time information",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "mobileHeight": null,
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "widgetStyle": {},
- "useDashboardTimewindow": false,
- "displayTimewindow": true,
- "actions": {}
- },
- "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155",
- "typeFullFqn": "system.charts.basic_timeseries"
- },
- "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
- "type": "timeseries",
- "sizeX": 7,
- "sizeY": 7,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "eventsSent",
- "type": "timeseries",
- "label": "Events",
- "color": "#2196f3",
- "settings": {
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.8156044798125357
- },
- {
- "name": "eventsProduced",
- "type": "timeseries",
- "label": "Produced",
- "color": "#4caf50",
- "settings": {
- "useCellStyleFunction": false,
- "useCellContentFunction": false
- },
- "_hash": 0.6538259344015449
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "realtime": {
- "interval": 1000,
- "timewindowMs": 604800000
- },
- "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": 6,
- "hideEmptyLines": true
- },
- "title": "Total Messages",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": false,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {},
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true,
- "legendConfig": {
- "direction": "column",
- "position": "bottom",
- "showMin": false,
- "showMax": false,
- "showAvg": true,
- "showTotal": false
- }
- },
- "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a",
- "typeFullFqn": "system.cards.timeseries_table"
- },
- "aaa69366-aacc-9028-65aa-645c0f8533ec": {
- "type": "timeseries",
- "sizeX": 17,
- "sizeY": 4,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "eventsSent",
- "type": "timeseries",
- "label": "eventsSent",
- "color": "#2196f3",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.41414001784591314
- },
- {
- "name": "eventsProduced",
- "type": "timeseries",
- "label": "eventsProduced",
- "color": "#4caf50",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.7819101846284422
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "realtime": {
- "timewindowMs": 60000
- }
- },
- "showTitle": true,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "shadowSize": 4,
- "fontColor": "#545454",
- "fontSize": 10,
- "xaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "yaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "grid": {
- "color": "#545454",
- "tickColor": "#DDDDDD",
- "verticalLines": true,
- "horizontalLines": true,
- "outlineWidth": 1
- },
- "stack": false,
- "tooltipIndividual": false,
- "timeForComparison": "months",
- "xaxisSecond": {
- "axisPosition": "top",
- "showLabels": true
- },
- "showLegend": true,
- "legendConfig": {
- "direction": "column",
- "position": "right",
- "showMin": true,
- "showMax": true,
- "showAvg": true,
- "showTotal": true
- }
- },
- "title": "History information",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "mobileHeight": null,
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "widgetStyle": {},
- "useDashboardTimewindow": true,
- "displayTimewindow": true,
- "actions": {}
- },
- "id": "aaa69366-aacc-9028-65aa-645c0f8533ec",
- "typeFullFqn": "system.charts.basic_timeseries"
- },
- "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
- "type": "timeseries",
- "sizeX": 17,
- "sizeY": 4,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "bleEventsProduced",
- "type": "timeseries",
- "label": "bleEventsProduced",
- "color": "#2196f3",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.5625165504526104
- },
- {
- "name": "bleEventsSent",
- "type": "timeseries",
- "label": "bleEventsSent",
- "color": "#4caf50",
- "settings": {
- "excludeFromStacking": false,
- "hideDataByDefault": false,
- "disableDataHiding": false,
- "removeFromLegend": false,
- "showLines": true,
- "fillLines": false,
- "showPoints": false,
- "showPointShape": "circle",
- "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
- "showPointsLineWidth": 5,
- "showPointsRadius": 3,
- "tooltipValueFormatter": "",
- "showSeparateAxis": false,
- "axisTitle": "",
- "axisPosition": "left",
- "axisTicksFormatter": "",
- "comparisonSettings": {
- "showValuesForComparison": true,
- "comparisonValuesLabel": "",
- "color": ""
- }
- },
- "_hash": 0.6817950080745288
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "realtime": {
- "interval": 5000,
- "timewindowMs": 120000
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": true,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "shadowSize": 4,
- "fontColor": "#545454",
- "fontSize": 10,
- "xaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "yaxis": {
- "showLabels": true,
- "color": "#545454"
- },
- "grid": {
- "color": "#545454",
- "tickColor": "#DDDDDD",
- "verticalLines": true,
- "horizontalLines": true,
- "outlineWidth": 1
- },
- "stack": false,
- "tooltipIndividual": false,
- "timeForComparison": "months",
- "xaxisSecond": {
- "axisPosition": "top",
- "showLabels": true
- },
- "showLegend": true,
- "legendConfig": {
- "direction": "column",
- "position": "right",
- "showMin": true,
- "showMax": true,
- "showAvg": true,
- "showTotal": true
- }
- },
- "title": "Real time information",
- "dropShadow": true,
- "enableFullscreen": true,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "mobileHeight": null,
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "widgetStyle": {},
- "useDashboardTimewindow": false,
- "displayTimewindow": true,
- "actions": {}
- },
- "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0",
- "typeFullFqn": "system.charts.basic_timeseries"
- },
- "466f046d-6005-a168-b107-60fcb2469cd5": {
- "type": "latest",
- "sizeX": 7,
- "sizeY": 5,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085270425,
- "endTimeMs": 1694171670425
- },
- "quickInterval": "CURRENT_DAY"
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": true,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "eventsTitle": "Gateway Events Form",
- "eventsReg": [
- "EventsProduced",
- "EventsSent"
- ]
- },
- "title": "Gateway events",
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "dropShadow": true,
- "enableFullscreen": true,
- "widgetStyle": {},
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "displayTimewindow": true,
- "showLegend": false,
- "actions": {}
- },
- "id": "466f046d-6005-a168-b107-60fcb2469cd5",
- "typeFullFqn": "system.gateway_widgets.attributes_card"
- },
- "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
- "type": "latest",
- "sizeX": 10,
- "sizeY": 9,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085270425,
- "endTimeMs": 1694171670425
- },
- "quickInterval": "CURRENT_DAY"
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": true,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "gatewayTitle": "Gateway configuration (Single device)",
- "readOnly": false
- },
- "title": "New Gateway configuration (Single device)",
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "dropShadow": true,
- "enableFullscreen": true,
- "widgetStyle": {},
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "displayTimewindow": true,
- "showLegend": false,
- "actions": {}
- },
- "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b",
- "typeFullFqn": "system.gateway_widgets.config_form_latest"
- },
- "063fc179-c9fd-f952-e714-f24e9c43c05c": {
- "type": "rpc",
- "sizeX": 4,
- "sizeY": 2,
- "config": {
- "targetDeviceAliases": [],
- "showTitle": false,
- "backgroundColor": "#e6e7e8",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "0px",
- "settings": {
- "requestTimeout": 5000,
- "oneWayElseTwoWay": true,
- "styleButton": {
- "isRaised": true,
- "isPrimary": false
- },
- "methodParams": "{}",
- "methodName": "gateway_reboot",
- "buttonText": "GATEWAY REBOOT"
- },
- "title": "New RPC Button",
- "dropShadow": true,
- "enableFullscreen": false,
- "widgetStyle": {},
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "actions": {},
- "datasources": [],
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true,
- "targetDeviceAliasIds": [
- "b2487e75-2fa4-f211-142c-434dfd50c70c"
- ]
- },
- "id": "063fc179-c9fd-f952-e714-f24e9c43c05c",
- "typeFullFqn": "system.control_widgets.rpcbutton"
- },
- "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
- "type": "rpc",
- "sizeX": 4,
- "sizeY": 2,
- "config": {
- "targetDeviceAliases": [],
- "showTitle": false,
- "backgroundColor": "#e6e7e8",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "0px",
- "settings": {
- "requestTimeout": 5000,
- "oneWayElseTwoWay": true,
- "styleButton": {
- "isRaised": true,
- "isPrimary": false
- },
- "methodName": "gateway_restart",
- "methodParams": "{}",
- "buttonText": "GATEWAY RESTART"
- },
- "title": "New RPC Button",
- "dropShadow": true,
- "enableFullscreen": false,
- "widgetStyle": {},
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "actions": {},
- "datasources": [],
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true,
- "targetDeviceAliasIds": [
- "b2487e75-2fa4-f211-142c-434dfd50c70c"
- ]
- },
- "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7",
- "typeFullFqn": "system.control_widgets.rpcbutton"
- },
- "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
- "type": "latest",
- "sizeX": 6,
- "sizeY": 4,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "latitude",
- "type": "attribute",
- "label": "latitude",
- "color": "#2196f3",
- "settings": {},
- "_hash": 0.9743324774725604
- },
- {
- "name": "longitude",
- "type": "attribute",
- "label": "longitude",
- "color": "#4caf50",
- "settings": {},
- "_hash": 0.5530093635101525
- }
- ],
- "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
- }
- ],
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085270425,
- "endTimeMs": 1694171670425
- },
- "quickInterval": "CURRENT_DAY"
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": false,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "fitMapBounds": true,
- "latKeyName": "latitude",
- "lngKeyName": "longitude",
- "showLabel": true,
- "label": "${entityName}",
- "tooltipPattern": "${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Delete",
- "markerImageSize": 34,
- "useColorFunction": false,
- "markerImages": [],
- "useMarkerImageFunction": false,
- "color": "#fe7569",
- "mapProvider": "OpenStreetMap.Mapnik",
- "showTooltip": true,
- "autocloseTooltip": true,
- "defaultCenterPosition": [
- 0,
- 0
- ],
- "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
- "showTooltipAction": "click",
- "polygonKeyName": "coordinates",
- "polygonOpacity": 0.5,
- "polygonStrokeOpacity": 1,
- "polygonStrokeWeight": 1,
- "zoomOnClick": true,
- "showCoverageOnHover": true,
- "animate": true,
- "maxClusterRadius": 80,
- "removeOutsideVisibleBounds": true,
- "defaultZoomLevel": 5
- },
- "title": "Gateway Location",
- "dropShadow": true,
- "enableFullscreen": false,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {
- "tooltipAction": [
- {
- "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
- "name": "delete",
- "icon": "more_horiz",
- "type": "custom",
- "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
- }
- ]
- },
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true
- },
- "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842",
- "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
- }
- },
- "states": {
- "main_gateway": {
- "name": "Gateways",
- "root": true,
- "layouts": {
- "main": {
- "widgets": {
- "94715984-ae74-76e4-20b7-2f956b01ed80": {
- "sizeX": 24,
- "sizeY": 12,
- "row": 0,
- "col": 0
- }
- },
- "gridSettings": {
- "backgroundColor": "#eeeeee",
- "color": "rgba(0,0,0,0.870588)",
- "columns": 24,
- "backgroundSizeMode": "100%",
- "autoFillHeight": true,
- "mobileAutoFillHeight": false,
- "mobileRowHeight": 70,
- "margin": 10,
- "outerMargin": true
- }
- }
- }
- },
- "__entityname__config": {
- "name": "${entityName} Configuration",
- "root": false,
- "layouts": {
- "main": {
- "widgets": {
- "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
- "sizeX": 14,
- "sizeY": 13,
- "row": 0,
- "col": 10
- },
- "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
- "sizeX": 10,
- "sizeY": 9,
- "row": 0,
- "col": 0
- },
- "063fc179-c9fd-f952-e714-f24e9c43c05c": {
- "sizeX": 4,
- "sizeY": 2,
- "row": 9,
- "col": 0
- },
- "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
- "sizeX": 4,
- "sizeY": 2,
- "row": 11,
- "col": 0
- },
- "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
- "sizeX": 6,
- "sizeY": 4,
- "row": 9,
- "col": 4
- }
- },
- "gridSettings": {
- "backgroundColor": "#eeeeee",
- "color": "rgba(0,0,0,0.870588)",
- "columns": 24,
- "backgroundSizeMode": "100%",
- "autoFillHeight": true,
- "mobileAutoFillHeight": false,
- "mobileRowHeight": 70,
- "margin": 10,
- "outerMargin": true
- }
- }
- }
- },
- "__entityname_grafic": {
- "name": "${entityName} Details",
- "root": false,
- "layouts": {
- "main": {
- "widgets": {
- "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
- "sizeX": 17,
- "sizeY": 4,
- "mobileHeight": null,
- "row": 4,
- "col": 7
- },
- "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
- "sizeX": 7,
- "sizeY": 7,
- "row": 5,
- "col": 0
- },
- "aaa69366-aacc-9028-65aa-645c0f8533ec": {
- "sizeX": 17,
- "sizeY": 4,
- "mobileHeight": null,
- "row": 0,
- "col": 7
- },
- "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
- "sizeX": 17,
- "sizeY": 4,
- "mobileHeight": null,
- "row": 8,
- "col": 7
- },
- "466f046d-6005-a168-b107-60fcb2469cd5": {
- "sizeX": 7,
- "sizeY": 5,
- "row": 0,
- "col": 0
- }
- },
- "gridSettings": {
- "backgroundColor": "#eeeeee",
- "color": "rgba(0,0,0,0.870588)",
- "columns": 24,
- "backgroundSizeMode": "auto 100%",
- "autoFillHeight": true,
- "mobileAutoFillHeight": true,
- "mobileRowHeight": 70,
- "margin": 10,
- "outerMargin": true
- }
- }
- }
- }
- },
- "entityAliases": {
- "3e0f533a-0db1-3292-184f-06e73535061a": {
- "id": "3e0f533a-0db1-3292-184f-06e73535061a",
- "alias": "Gateways",
- "filter": {
- "type": "deviceType",
- "resolveMultiple": true,
- "deviceNameFilter": "",
- "deviceTypes": [
- "gateway"
- ]
- }
- },
- "b2487e75-2fa4-f211-142c-434dfd50c70c": {
- "id": "b2487e75-2fa4-f211-142c-434dfd50c70c",
- "alias": "Current Gateway",
- "filter": {
- "type": "stateEntity",
- "resolveMultiple": false,
- "stateEntityParamName": "",
- "defaultStateEntity": null
- }
- }
- },
- "timewindow": {
- "realtime": {
- "interval": 1000,
- "timewindowMs": 86400000
- },
- "aggregation": {
- "type": "NONE",
- "limit": 25000
- },
- "hideInterval": false,
- "hideAggregation": false,
- "hideAggInterval": false
- },
- "settings": {
- "stateControllerId": "entity",
- "showTitle": true,
- "showDashboardsSelect": true,
- "showEntitiesSelect": true,
- "showDashboardTimewindow": true,
- "showDashboardExport": true,
- "toolbarAlwaysOpen": true,
- "titleColor": "rgba(0,0,0,0.870588)"
- },
- "filters": {}
- },
- "externalId": null,
- "name": "Gateways"
-}
\ No newline at end of file
diff --git a/application/src/main/data/json/demo/dashboards/gateway.json b/application/src/main/data/json/tenant/dashboards/gateways.json
similarity index 99%
rename from application/src/main/data/json/demo/dashboards/gateway.json
rename to application/src/main/data/json/tenant/dashboards/gateways.json
index b35b480ef2..972aed299f 100644
--- a/application/src/main/data/json/demo/dashboards/gateway.json
+++ b/application/src/main/data/json/tenant/dashboards/gateways.json
@@ -1,5 +1,5 @@
{
- "title": "Gateway",
+ "title": "ThingsBoard IoT Gateways",
"image": null,
"mobileHide": false,
"mobileOrder": null,
@@ -40,7 +40,7 @@
"color": "rgba(0, 0, 0, 0.87)",
"padding": "4px",
"settings": {
- "entitiesTitle": "Gateway list",
+ "entitiesTitle": "Gateways list",
"enableSearch": true,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
@@ -55,7 +55,7 @@
"defaultSortOrder": "entityName",
"useRowStyleFunction": false
},
- "title": "New Entities table",
+ "title": "Gateways list",
"dropShadow": true,
"enableFullscreen": false,
"titleStyle": {
@@ -571,11 +571,11 @@
"padding": "8px",
"settings": {
"useMarkdownTextFunction": true,
- "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connecotrs\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n \n \n ${generateMatHeader(index)}\n ${label}\n \n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Active | Inactive)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `${blockData}
`;",
+ "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n \n \n ${generateMatHeader(index)}\n ${label}\n \n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Active | Inactive)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `${blockData}
`;",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}"
},
- "title": "New Markdown/HTML Card",
+ "title": "Connectors",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
@@ -599,7 +599,7 @@
"actions": {
"elementClick": [
{
- "name": "Connecotrs",
+ "name": "Connectors",
"icon": "more_horiz",
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
@@ -680,7 +680,7 @@
"defaultSortOrder": "-createdTime",
"useRowStyleFunction": false
},
- "title": "New Alarms table",
+ "title": "Alarms",
"dropShadow": true,
"enableFullscreen": false,
"titleStyle": {
@@ -1074,7 +1074,7 @@
}
]
},
- "title": "New RPC remote shell",
+ "title": "RPC remote shell",
"dropShadow": true,
"enableFullscreen": true,
"widgetStyle": {
@@ -1485,7 +1485,7 @@
}
]
},
- "title": "New RPC debug terminal",
+ "title": "RPC debug terminal",
"dropShadow": true,
"enableFullscreen": true,
"widgetStyle": {},
@@ -1868,7 +1868,7 @@
"applyDefaultMarkdownStyle": false,
"markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: center;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
},
- "title": "New Markdown/HTML Card",
+ "title": "Service command",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
@@ -1980,7 +1980,7 @@
"applyDefaultMarkdownStyle": false,
"markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: start;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
},
- "title": "New Markdown/HTML Card",
+ "title": "General configuration",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
@@ -2145,7 +2145,7 @@
"applyDefaultMarkdownStyle": true,
"markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}"
},
- "title": "New Markdown/HTML Card",
+ "title": "Gateway devices",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
@@ -4939,7 +4939,7 @@
"applyDefaultMarkdownStyle": false,
"markdownCss": ".action-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
},
- "title": "New Markdown/HTML Card",
+ "title": "Gateway commands",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
index 48425d58db..d69b9aec0b 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
@@ -64,6 +64,9 @@ public class DeviceActor extends ContextAwareActor {
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processAttributesUpdate((DeviceAttributesEventNotificationMsg) msg);
break;
+ case DEVICE_DELETE_TO_DEVICE_ACTOR_MSG:
+ ctx.stop(ctx.getSelf());
+ break;
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processCredentialsUpdate(msg);
break;
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index c9f5448df3..86f338e89d 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -53,10 +53,13 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@Slf4j
public class TenantActor extends RuleChainManagerActor {
@@ -65,8 +68,11 @@ public class TenantActor extends RuleChainManagerActor {
private boolean isCore;
private ApiUsageState apiUsageState;
+ private Set deletedDevices;
+
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
+ this.deletedDevices = new HashSet<>();
}
boolean cantFindTenant = false;
@@ -221,6 +227,10 @@ public class TenantActor extends RuleChainManagerActor {
if (!isCore) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg);
}
+ if (deletedDevices.contains(msg.getDeviceId())) {
+ log.debug("RECEIVED MESSAGE FOR DELETED DEVICE: {}", msg);
+ return;
+ }
TbActorRef deviceActor = getOrCreateDeviceActor(msg.getDeviceId());
if (priority) {
deviceActor.tellWithHighPriority(msg);
@@ -240,7 +250,8 @@ public class TenantActor extends RuleChainManagerActor {
log.info("[{}] Received API state update. Going to ENABLE Rule Engine execution.", tenantId);
initRuleChains();
}
- } else if (msg.getEntityId().getEntityType() == EntityType.EDGE) {
+ }
+ if (msg.getEntityId().getEntityType() == EntityType.EDGE) {
EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
@@ -249,7 +260,13 @@ public class TenantActor extends RuleChainManagerActor {
Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
edgeRpcService.updateEdge(tenantId, edge);
}
- } else if (isRuleEngine) {
+ }
+ if (msg.getEntityId().getEntityType() == EntityType.DEVICE && ComponentLifecycleEvent.DELETED == msg.getEvent()) {
+ DeviceId deviceId = (DeviceId) msg.getEntityId();
+ onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true);
+ deletedDevices.add(deviceId);
+ }
+ if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
index 4d227345b1..e0d5d3e04e 100644
--- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
+++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
@@ -38,6 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
+import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames;
import org.thingsboard.server.service.security.model.token.OAuth2AppTokenFactory;
import org.thingsboard.server.utils.MiscUtils;
@@ -51,6 +52,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+@TbCoreComponent
@Service
@Slf4j
public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
diff --git a/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
new file mode 100644
index 0000000000..7b3a752f57
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@Order(SecurityProperties.BASIC_AUTH_ORDER)
+@ConditionalOnExpression("'${service.type:null}'=='tb-rule-engine'")
+public class TbRuleEngineSecurityConfiguration {
+
+ @Bean
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.headers().cacheControl().and().frameOptions().disable()
+ .and().cors().and().csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/actuator/prometheus").permitAll()
+ .anyRequest().authenticated();
+ return http.build();
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java
index 83d4d16034..dca113621c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java
@@ -106,7 +106,7 @@ public class DeviceConnectivityController extends BaseController {
@RequestMapping(value = "/device-connectivity/gateway-launch/{deviceId}", method = RequestMethod.GET)
@ResponseBody
public JsonNode getGatewayLaunchCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
- @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException {
+ @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException {
checkParameter(DEVICE_ID, strDeviceId);
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS);
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 f90a196edf..0109a327e8 100644
--- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
@@ -216,7 +216,7 @@ public class WidgetTypeController extends AutoCommitController {
@ApiOperation(value = "Get all Widget types for specified Bundle (getBundleWidgetTypes)",
notes = "Returns an array of Widget Type objects that belong to specified Widget Bundle." + WIDGET_TYPE_DESCRIPTION + " " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
- @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetTypes", params = {"widgetsBundleId"}, method = RequestMethod.GET)
@ResponseBody
public List getBundleWidgetTypes(
@@ -248,7 +248,7 @@ public class WidgetTypeController extends AutoCommitController {
@ApiOperation(value = "Get all Widget types details for specified Bundle (getBundleWidgetTypes)",
notes = "Returns an array of Widget Type Details objects that belong to specified Widget Bundle." + WIDGET_TYPE_DETAILS_DESCRIPTION + " " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
- @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetTypesDetails", params = {"widgetsBundleId"}, method = RequestMethod.GET)
@ResponseBody
public List getBundleWidgetTypesDetails(
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
index af9f72e819..cd23f19556 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
@@ -294,7 +294,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
private ListenableFuture> findRelationByQuery(TenantId tenantId, Edge edge,
EntityId entityId, EntitySearchDirection direction) {
EntityRelationsQuery query = new EntityRelationsQuery();
- query.setParameters(new RelationsSearchParameters(entityId, direction, -1, false));
+ query.setParameters(new RelationsSearchParameters(entityId, direction, 1, false));
return relationService.findByQuery(tenantId, query);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
index 72e8f44020..8682d18a62 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
@@ -112,7 +112,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
public void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device,
User user, Object... additionalInfo) {
gatewayNotificationsService.onDeviceDeleted(device);
- tbClusterService.onDeviceDeleted(device, null);
+ tbClusterService.onDeviceDeleted(tenantId, device, null);
logEntityAction(tenantId, deviceId, device, customerId, ActionType.DELETED, user, additionalInfo);
}
@@ -126,6 +126,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
@Override
public void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId,
Device device, Tenant tenant, User user, Object... additionalInfo) {
+ tbClusterService.onDeviceAssignedToTenant(tenantId, device);
logEntityAction(tenantId, deviceId, device, customerId, ActionType.ASSIGNED_TO_TENANT, user, additionalInfo);
pushAssignedFromNotification(tenant, newTenantId, device);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
index 19d4126989..3267ed1c17 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
@@ -54,6 +54,7 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
if (created) {
installScripts.createDefaultRuleChains(savedTenant.getId());
installScripts.createDefaultEdgeRuleChains(savedTenant.getId());
+ installScripts.createDefaultTenantDashboards(savedTenant.getId(), null);
}
tenantProfileCache.evict(savedTenant.getId());
notificationEntityService.notifyCreateOrUpdateTenant(savedTenant, created ?
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 3e23f9af37..072ce349e7 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
@@ -299,6 +299,15 @@ public class InstallScripts {
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
+ loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
+ }
+
+ public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
+ Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR);
+ loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
+ }
+
+ private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) throws IOException {
try (DirectoryStream dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
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 f96e49930c..af2a54a7ac 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
@@ -308,10 +308,17 @@ public class DefaultTbClusterService implements TbClusterService {
}
@Override
- public void onDeviceDeleted(Device device, TbQueueCallback callback) {
- broadcastEntityDeleteToTransport(device.getTenantId(), device.getId(), device.getName(), callback);
- sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), false, false, true);
- broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), ComponentLifecycleEvent.DELETED);
+ public void onDeviceDeleted(TenantId tenantId, Device device, TbQueueCallback callback) {
+ DeviceId deviceId = device.getId();
+ broadcastEntityDeleteToTransport(tenantId, deviceId, device.getName(), callback);
+ sendDeviceStateServiceEvent(tenantId, deviceId, false, false, true);
+ broadcastEntityStateChangeEvent(tenantId, deviceId, ComponentLifecycleEvent.DELETED);
+ }
+
+ @Override
+ public void onDeviceAssignedToTenant(TenantId oldTenantId, Device device) {
+ onDeviceDeleted(oldTenantId, device, null);
+ sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), true, false, false);
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java b/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java
index 36038568bc..946993151d 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java
@@ -46,6 +46,7 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg;
+import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
@@ -384,6 +385,21 @@ public class ProtoUtils {
);
}
+ private static TransportProtos.DeviceDeleteMsgProto toProto(DeviceDeleteMsg msg) {
+ return TransportProtos.DeviceDeleteMsgProto.newBuilder()
+ .setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits())
+ .setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits())
+ .setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits())
+ .setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits())
+ .build();
+ }
+
+ private static DeviceDeleteMsg fromProto(TransportProtos.DeviceDeleteMsgProto proto) {
+ return new DeviceDeleteMsg(
+ TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
+ new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())));
+ }
+
public static TransportProtos.ToDeviceActorNotificationMsgProto toProto(ToDeviceActorNotificationMsg msg) {
if (msg instanceof DeviceEdgeUpdateMsg) {
DeviceEdgeUpdateMsg updateMsg = (DeviceEdgeUpdateMsg) msg;
@@ -413,6 +429,10 @@ public class ProtoUtils {
RemoveRpcActorMsg updateMsg = (RemoveRpcActorMsg) msg;
TransportProtos.RemoveRpcActorMsgProto proto = toProto(updateMsg);
return TransportProtos.ToDeviceActorNotificationMsgProto.newBuilder().setRemoveRpcActorMsg(proto).build();
+ } else if (msg instanceof DeviceDeleteMsg) {
+ DeviceDeleteMsg updateMsg = (DeviceDeleteMsg) msg;
+ TransportProtos.DeviceDeleteMsgProto proto = toProto(updateMsg);
+ return TransportProtos.ToDeviceActorNotificationMsgProto.newBuilder().setDeviceDeleteMsg(proto).build();
}
return null;
}
@@ -432,6 +452,8 @@ public class ProtoUtils {
return fromProto(proto.getFromDeviceRpcResponseMsg());
} else if (proto.hasRemoveRpcActorMsg()) {
return fromProto(proto.getRemoveRpcActorMsg());
+ } else if (proto.hasDeviceDeleteMsg()) {
+ return fromProto(proto.getDeviceDeleteMsg());
}
return null;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java
index d63f759dd3..98ccb2c1be 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java
@@ -18,11 +18,13 @@ package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.stereotype.Component;
+import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
+@TbCoreComponent
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository {
public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request";
public static final String PREV_URI_PARAMETER = "prevUri";
diff --git a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
index 6f0d6f9465..fb8064d6d5 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
@@ -327,7 +327,18 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test
public void testFindTenantDashboards() throws Exception {
- List dashboards = new ArrayList<>();
+ List expectedDashboards = new ArrayList<>();
+ PageLink pageLink = new PageLink(24);
+ PageData pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
+ new TypeReference>() {
+ }, pageLink);
+ expectedDashboards.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageLink.nextPageLink();
+ }
+ } while (pageData.hasNext());
Mockito.reset(tbClusterService, auditLogService);
@@ -335,7 +346,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
for (int i = 0; i < cntEntity; i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard" + i);
- dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
+ expectedDashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(),
@@ -343,8 +354,6 @@ public class DashboardControllerTest extends AbstractControllerTest {
ActionType.ADDED, cntEntity, cntEntity, cntEntity);
List loadedDashboards = new ArrayList<>();
- PageLink pageLink = new PageLink(24);
- PageData pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference>() {
@@ -355,10 +364,10 @@ public class DashboardControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
- dashboards.sort(idComparator);
+ expectedDashboards.sort(idComparator);
loadedDashboards.sort(idComparator);
- Assert.assertEquals(dashboards, loadedDashboards);
+ Assert.assertEquals(expectedDashboards, loadedDashboards);
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
index 227d37f483..f517d2e9c5 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
@@ -18,8 +18,6 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -32,7 +30,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@@ -46,7 +43,6 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceProfileId;
-import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@@ -58,14 +54,16 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CA_ROOT_CERT_PEM;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS;
+import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS;
-import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CA_ROOT_CERT_PEM;
+import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS;
@TestPropertySource(properties = {
"device.connectivity.mqtts.pem_cert_file=/tmp/" + CA_ROOT_CERT_PEM
@@ -294,6 +292,140 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
}
+ @Test
+ public void testFetchGatewayLaunchCommands() throws Exception {
+ Device device = new Device();
+ device.setName("My device");
+ device.setType("default");
+ ObjectNode additionalInfo = JacksonUtil.newObjectNode();
+ additionalInfo.put("gateway", true);
+ device.setAdditionalInfo(additionalInfo);
+ Device savedDevice = doPost("/api/device", device, Device.class);
+ DeviceCredentials credentials =
+ doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
+
+ JsonNode commands =
+ doGetTyped("/api/device-connectivity/gateway-launch/" + savedDevice.getId().getId(), new TypeReference<>() {
+ });
+
+ JsonNode dockerMqttCommands = commands.get(MQTT);
+ assertThat(dockerMqttCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=1883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
+ assertThat(dockerMqttCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=1883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
+
+ JsonNode dockerMqttsCommands = commands.get(MQTTS);
+ assertThat(dockerMqttsCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=8883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
+ assertThat(dockerMqttsCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=8883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
+ }
+
+ @Test
+ public void testFetchPublishTelemetryCommandsForDeviceWithIpV6LocalhostAddress() throws Exception {
+ loginSysAdmin();
+ setConnectivityHost("::1");
+ loginTenantAdmin();
+
+ Device device = new Device();
+ device.setName("My device");
+ device.setType("default");
+ Device savedDevice = doPost("/api/device", device, Device.class);
+ DeviceCredentials credentials =
+ doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
+
+ JsonNode commands =
+ doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
+ });
+
+ assertThat(commands).hasSize(3);
+ JsonNode httpCommands = commands.get(HTTP);
+ assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://[::1]:8080/api/v1/%s/telemetry " +
+ "--header Content-Type:application/json --data \"{temperature:25}\"",
+ credentials.getCredentialsId()));
+ assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://[::1]/api/v1/%s/telemetry " +
+ "--header Content-Type:application/json --data \"{temperature:25}\"",
+ credentials.getCredentialsId()));
+
+ JsonNode mqttCommands = commands.get(MQTT);
+ assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h ::1 -p 1883 -t v1/devices/me/telemetry " +
+ "-u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download");
+ assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile ca-root.pem -h ::1 -p 8883 " +
+ "-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+
+ JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
+ assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h ::1" +
+ " -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
+ "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
+ "mosquitto_pub -d -q 1 --cafile ca-root.pem -h ::1 -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
+ credentials.getCredentialsId()));
+
+ JsonNode linuxCoapCommands = commands.get(COAP);
+ assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://[::1]:5683/api/v1/%s/telemetry " +
+ "-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://[::1]:5684/api/v1/%s/telemetry" +
+ " -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+
+ JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
+ assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
+ " thingsboard/coap-clients coap-client -v 6 -m POST coap://[::1]:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
+ " thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://[::1]:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+ }
+
+ @Test
+ public void testFetchPublishTelemetryCommandsForDeviceWithIpV6Address() throws Exception {
+
+ loginSysAdmin();
+ setConnectivityHost("1:1:1:1:1:1:1:1");
+ loginTenantAdmin();
+
+ Device device = new Device();
+ device.setName("My device");
+ device.setType("default");
+ Device savedDevice = doPost("/api/device", device, Device.class);
+ DeviceCredentials credentials =
+ doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
+
+ JsonNode commands =
+ doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
+ });
+ assertThat(commands).hasSize(3);
+ JsonNode httpCommands = commands.get(HTTP);
+ assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://[1:1:1:1:1:1:1:1]:8080/api/v1/%s/telemetry " +
+ "--header Content-Type:application/json --data \"{temperature:25}\"",
+ credentials.getCredentialsId()));
+ assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://[1:1:1:1:1:1:1:1]/api/v1/%s/telemetry " +
+ "--header Content-Type:application/json --data \"{temperature:25}\"",
+ credentials.getCredentialsId()));
+
+ JsonNode mqttCommands = commands.get(MQTT);
+ assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h 1:1:1:1:1:1:1:1 -p 1883 -t v1/devices/me/telemetry " +
+ "-u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download");
+ assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile ca-root.pem -h 1:1:1:1:1:1:1:1 -p 8883 " +
+ "-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+
+ JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
+ assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h 1:1:1:1:1:1:1:1" +
+ " -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
+ "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
+ "mosquitto_pub -d -q 1 --cafile ca-root.pem -h 1:1:1:1:1:1:1:1 -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
+ credentials.getCredentialsId()));
+
+ JsonNode linuxCoapCommands = commands.get(COAP);
+ assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://[1:1:1:1:1:1:1:1]:5683/api/v1/%s/telemetry " +
+ "-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://[1:1:1:1:1:1:1:1]:5684/api/v1/%s/telemetry" +
+ " -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+
+ JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
+ assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it" +
+ " thingsboard/coap-clients coap-client -v 6 -m POST coap://[1:1:1:1:1:1:1:1]:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+ assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it" +
+ " thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://[1:1:1:1:1:1:1:1]:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
+
+ }
+
@Test
public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception {
Device device = new Device();
@@ -518,47 +650,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
public void testFetchPublishTelemetryCommandsForDefaultDeviceIfHostIsNotLocalhost() throws Exception {
loginSysAdmin();
- ObjectNode config = JacksonUtil.newObjectNode();
-
- ObjectNode http = JacksonUtil.newObjectNode();
- http.put("enabled", true);
- http.put("host", "test.domain");
- http.put("port", 8080);
- config.set("http", http);
-
- ObjectNode https = JacksonUtil.newObjectNode();
- https.put("enabled", true);
- https.put("host", "test.domain");
- https.put("port", 443);
- config.set("https", https);
-
- ObjectNode mqtt = JacksonUtil.newObjectNode();
- mqtt.put("enabled", true);
- mqtt.put("host", "test.domain");
- mqtt.put("port", 1883);
- config.set("mqtt", mqtt);
-
- ObjectNode mqtts = JacksonUtil.newObjectNode();
- mqtts.put("enabled", true);
- mqtts.put("host", "test.domain");
- mqtts.put("port", 8883);
- config.set("mqtts", mqtts);
-
- ObjectNode coap = JacksonUtil.newObjectNode();
- coap.put("enabled", true);
- coap.put("host", "test.domain");
- coap.put("port", 5683);
- config.set("coap", coap);
-
- ObjectNode coaps = JacksonUtil.newObjectNode();
- coaps.put("enabled", true);
- coaps.put("host", "test.domain");
- coaps.put("port", 5684);
- config.set("coaps", coaps);
-
- AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
- adminSettings.setJsonValue(config);
- doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
+ setConnectivityHost("test.domain");
login("tenant2@thingsboard.org", "testPassword1");
@@ -612,4 +704,49 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it " +
"thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
+
+
+ private void setConnectivityHost(String host) throws Exception {
+ ObjectNode config = JacksonUtil.newObjectNode();
+
+ ObjectNode http = JacksonUtil.newObjectNode();
+ http.put("enabled", true);
+ http.put("host", host);
+ http.put("port", 8080);
+ config.set("http", http);
+
+ ObjectNode https = JacksonUtil.newObjectNode();
+ https.put("enabled", true);
+ https.put("host", host);
+ https.put("port", 443);
+ config.set("https", https);
+
+ ObjectNode mqtt = JacksonUtil.newObjectNode();
+ mqtt.put("enabled", true);
+ mqtt.put("host", host);
+ mqtt.put("port", 1883);
+ config.set("mqtt", mqtt);
+
+ ObjectNode mqtts = JacksonUtil.newObjectNode();
+ mqtts.put("enabled", true);
+ mqtts.put("host", host);
+ mqtts.put("port", 8883);
+ config.set("mqtts", mqtts);
+
+ ObjectNode coap = JacksonUtil.newObjectNode();
+ coap.put("enabled", true);
+ coap.put("host", host);
+ coap.put("port", 5683);
+ config.set("coap", coap);
+
+ ObjectNode coaps = JacksonUtil.newObjectNode();
+ coaps.put("enabled", true);
+ coaps.put("host", host);
+ coaps.put("port", 5684);
+ config.set("coaps", coaps);
+
+ AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
+ adminSettings.setJsonValue(config);
+ doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
+ }
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
index 778a11a552..7cafe2bbcb 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
@@ -28,12 +28,14 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
+import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
@@ -51,7 +53,6 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
-import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -73,7 +74,9 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DaoSqlTest;
+import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
+import org.thingsboard.server.service.state.DeviceStateService;
import java.util.ArrayList;
import java.util.List;
@@ -82,6 +85,7 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -106,6 +110,9 @@ public class DeviceControllerTest extends AbstractControllerTest {
@SpyBean
private GatewayNotificationsService gatewayNotificationsService;
+ @SpyBean
+ private DeviceStateService deviceStateService;
+
@Autowired
private DeviceDao deviceDao;
@@ -1340,6 +1347,28 @@ public class DeviceControllerTest extends AbstractControllerTest {
ActionType.ASSIGNED_TO_TENANT, savedDifferentTenant.getId().getId().toString(), savedDifferentTenant.getTitle());
testNotificationUpdateGatewayNever();
+ ArgumentCaptor protoCaptor = ArgumentCaptor.forClass(TransportProtos.DeviceStateServiceMsgProto.class);
+
+ Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> {
+ Mockito.verify(deviceStateService, Mockito.atLeastOnce()).onQueueMsg(protoCaptor.capture(), any());
+ return protoCaptor.getAllValues().stream().anyMatch(proto ->
+ proto.getTenantIdMSB() == savedTenant.getUuidId().getMostSignificantBits() &&
+ proto.getTenantIdLSB() == savedTenant.getUuidId().getLeastSignificantBits() &&
+ proto.getDeviceIdMSB() == savedDevice.getUuidId().getMostSignificantBits() &&
+ proto.getDeviceIdLSB() == savedDevice.getUuidId().getLeastSignificantBits() &&
+ proto.getDeleted());
+ });
+
+ Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> {
+ Mockito.verify(deviceStateService, Mockito.atLeastOnce()).onQueueMsg(protoCaptor.capture(), any());
+ return protoCaptor.getAllValues().stream().anyMatch(proto ->
+ proto.getTenantIdMSB() == savedDifferentTenant.getUuidId().getMostSignificantBits() &&
+ proto.getTenantIdLSB() == savedDifferentTenant.getUuidId().getLeastSignificantBits() &&
+ proto.getDeviceIdMSB() == savedDevice.getUuidId().getMostSignificantBits() &&
+ proto.getDeviceIdLSB() == savedDevice.getUuidId().getLeastSignificantBits() &&
+ proto.getAdded());
+ });
+
login("tenant9@thingsboard.org", "testPassword1");
Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId(), Device.class);
diff --git a/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
index d020fef1cb..148c4afea9 100644
--- a/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
@@ -92,6 +92,8 @@ public class HomePageApiTest extends AbstractControllerTest {
@MockBean
private SmsService smsService;
+ private static final int DEFAULT_DASHBOARDS_COUNT = 1;
+
//For system administrator
@Test
public void testTenantsCountWsCmd() throws Exception {
@@ -408,7 +410,7 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(2, usageInfo.getUsers());
Assert.assertEquals(configuration.getMaxUsers(), usageInfo.getMaxUsers());
- Assert.assertEquals(0, usageInfo.getDashboards());
+ Assert.assertEquals(DEFAULT_DASHBOARDS_COUNT, usageInfo.getDashboards());
Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards());
Assert.assertEquals(0, usageInfo.getTransportMessages());
@@ -478,7 +480,8 @@ public class HomePageApiTest extends AbstractControllerTest {
}
usageInfo = doGet("/api/usage", UsageInfo.class);
- Assert.assertEquals(dashboards.size(), usageInfo.getDashboards());
+ int expectedDashboardsCount = dashboards.size() + DEFAULT_DASHBOARDS_COUNT;
+ Assert.assertEquals(expectedDashboardsCount, usageInfo.getDashboards());
}
private Long getInitialEntityCount(EntityType entityType) throws Exception {
diff --git a/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
index 9cc9fd0596..e515250321 100644
--- a/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
@@ -190,6 +190,32 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
Collections.sort(loadedWidgetTypes, idComparator);
Assert.assertEquals(widgetTypes, loadedWidgetTypes);
+
+ loginCustomerUser();
+
+ List loadedWidgetTypesCustomer = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
+ new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
+ Collections.sort(loadedWidgetTypesCustomer, idComparator);
+ Assert.assertEquals(widgetTypes, loadedWidgetTypesCustomer);
+
+ List customerLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
+ new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
+ List widgetTypesFromDetailsListCustomer = customerLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
+ Collections.sort(widgetTypesFromDetailsListCustomer, idComparator);
+ Assert.assertEquals(widgetTypesFromDetailsListCustomer, loadedWidgetTypes);
+
+ loginSysAdmin();
+
+ List sysAdminLoadedWidgetTypes = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
+ new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
+ Collections.sort(sysAdminLoadedWidgetTypes, idComparator);
+ Assert.assertEquals(widgetTypes, sysAdminLoadedWidgetTypes);
+
+ List sysAdminLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
+ new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
+ List widgetTypesFromDetailsListSysAdmin = sysAdminLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
+ Collections.sort(widgetTypesFromDetailsListSysAdmin, idComparator);
+ Assert.assertEquals(widgetTypesFromDetailsListSysAdmin, loadedWidgetTypes);
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java
index f9cb351aa0..5fc532ece5 100644
--- a/application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java
@@ -112,14 +112,24 @@ public class RelationEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1");
Asset asset = findAssetByName("Edge Asset 1");
- EntityRelation relation = new EntityRelation();
- relation.setType("test");
- relation.setFrom(device.getId());
- relation.setTo(asset.getId());
- relation.setTypeGroup(RelationTypeGroup.COMMON);
+ EntityRelation deviceToAssetRelation = new EntityRelation();
+ deviceToAssetRelation.setType("test");
+ deviceToAssetRelation.setFrom(device.getId());
+ deviceToAssetRelation.setTo(asset.getId());
+ deviceToAssetRelation.setTypeGroup(RelationTypeGroup.COMMON);
edgeImitator.expectMessageAmount(1);
- doPost("/api/relation", relation);
+ doPost("/api/relation", deviceToAssetRelation);
+ Assert.assertTrue(edgeImitator.waitForMessages());
+
+ EntityRelation assetToTenantRelation = new EntityRelation();
+ assetToTenantRelation.setType("test");
+ assetToTenantRelation.setFrom(asset.getId());
+ assetToTenantRelation.setTo(tenantId);
+ assetToTenantRelation.setTypeGroup(RelationTypeGroup.COMMON);
+
+ edgeImitator.expectMessageAmount(1);
+ doPost("/api/relation", assetToTenantRelation);
Assert.assertTrue(edgeImitator.waitForMessages());
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@@ -143,7 +153,7 @@ public class RelationEdgeTest extends AbstractEdgeTest {
RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage;
EntityRelation entityRelation = JacksonUtil.fromStringIgnoreUnknownProperties(relationUpdateMsg.getEntity(), EntityRelation.class);
Assert.assertNotNull(entityRelation);
- Assert.assertEquals(relation, entityRelation);
+ Assert.assertEquals(deviceToAssetRelation, entityRelation);
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType());
}
diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
index c5af954392..01a18ad7ad 100644
--- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
+++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.cache;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -27,6 +28,7 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.thingsboard.server.common.data.FstStatsService;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.JedisClusterCRC16;
@@ -44,6 +46,9 @@ public abstract class RedisTbTransactionalCache clazz);
+
+ void incrementDecode(Class> clazz);
+
+ void recordEncodeTime(Class> clazz, long startTime);
+
+ void recordDecodeTime(Class> clazz, long startTime);
+
+}
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
index 0a39d8d51b..c3d3623d8e 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
@@ -93,6 +93,8 @@ public enum MsgType {
DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG,
+ DEVICE_DELETE_TO_DEVICE_ACTOR_MSG,
+
DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG,
DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG,
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/rule/engine/DeviceDeleteMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/rule/engine/DeviceDeleteMsg.java
new file mode 100644
index 0000000000..1bdab7409b
--- /dev/null
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/rule/engine/DeviceDeleteMsg.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.msg.rule.engine;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
+
+@Data
+public class DeviceDeleteMsg implements ToDeviceActorNotificationMsg {
+
+ private static final long serialVersionUID = 4679029228395462172L;
+
+ private final TenantId tenantId;
+ private final DeviceId deviceId;
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.DEVICE_DELETE_TO_DEVICE_ACTOR_MSG;
+ }
+}
diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java
index b0d0b182e9..204502068c 100644
--- a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java
+++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java
@@ -17,8 +17,10 @@ package org.thingsboard.server.queue.util;
import lombok.extern.slf4j.Slf4j;
import org.nustaq.serialization.FSTConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.FSTUtils;
+import org.thingsboard.server.common.data.FstStatsService;
import java.util.Optional;
@@ -26,12 +28,21 @@ import java.util.Optional;
@Service
public class ProtoWithFSTService implements DataDecodingEncodingService {
+ @Autowired
+ private FstStatsService fstStatsService;
+
public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration();
@Override
public Optional decode(byte[] byteArray) {
try {
- return Optional.ofNullable(FSTUtils.decode(byteArray));
+ long startTime = System.nanoTime();
+ Optional optional = Optional.ofNullable(FSTUtils.decode(byteArray));
+ optional.ifPresent(obj -> {
+ fstStatsService.recordDecodeTime(obj.getClass(), startTime);
+ fstStatsService.incrementDecode(obj.getClass());
+ });
+ return optional;
} catch (IllegalArgumentException e) {
log.error("Error during deserialization message, [{}]", e.getMessage());
return Optional.empty();
@@ -41,7 +52,11 @@ public class ProtoWithFSTService implements DataDecodingEncodingService {
@Override
public byte[] encode(T msq) {
- return FSTUtils.encode(msq);
+ long startTime = System.nanoTime();
+ var bytes = FSTUtils.encode(msq);
+ fstStatsService.recordEncodeTime(msq.getClass(), startTime);
+ fstStatsService.incrementEncode(msq.getClass());
+ return bytes;
}
diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
index 31bd634221..4a3f2d306b 100644
--- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
+++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
@@ -182,12 +182,12 @@ public class TbUtils {
return TbJson.parse(ctx, jsonStr);
}
- public static String bytesToString(List bytesList) {
+ public static String bytesToString(List> bytesList) {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes);
}
- public static String bytesToString(List bytesList, String charsetName) throws UnsupportedEncodingException {
+ public static String bytesToString(List> bytesList, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes, charsetName);
}
@@ -210,10 +210,20 @@ public class TbUtils {
}
}
- private static byte[] bytesFromList(List bytesList) {
+ private static byte[] bytesFromList(List> bytesList) {
byte[] bytes = new byte[bytesList.size()];
for (int i = 0; i < bytesList.size(); i++) {
- bytes[i] = bytesList.get(i);
+ Object objectVal = bytesList.get(i);
+ if (objectVal instanceof Integer) {
+ bytes[i] = isValidIntegerToByte((Integer) objectVal);
+ } else if (objectVal instanceof String) {
+ bytes[i] = isValidIntegerToByte(parseInt((String) objectVal));
+ } else if (objectVal instanceof Byte) {
+ bytes[i] = (byte) objectVal;
+ } else {
+ throw new NumberFormatException("The value '" + objectVal + "' could not be correctly converted to a byte. " +
+ "Must be a HexDecimal/String/Integer/Byte format !");
+ }
}
return bytes;
}
@@ -643,7 +653,7 @@ public class TbUtils {
}
}
- public static boolean isValidRadix(String value, int radix) {
+ private static boolean isValidRadix(String value, int radix) {
for (int i = 0; i < value.length(); i++) {
if (i == 0 && value.charAt(i) == '-') {
if (value.length() == 1)
@@ -657,4 +667,12 @@ public class TbUtils {
return true;
}
+ private static byte isValidIntegerToByte (Integer val) {
+ if (val > 255 || val.intValue() < -128) {
+ throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +
+ "Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!");
+ } else {
+ return val.byteValue();
+ }
+ }
}
diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
index d2900d6ec4..7d5ae00dcf 100644
--- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
+++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
@@ -31,6 +31,7 @@ import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Random;
@@ -385,6 +386,95 @@ public class TbUtilsTest {
Assert.assertThrows(IllegalAccessException.class, () -> TbUtils.stringToBytes(ctx, ((ExecutionHashMap) finalInputJson).get("hello"), "UTF-8"));
}
+ @Test
+ public void bytesFromList() {
+ byte[] arrayBytes = {(byte)0x00, (byte)0x08, (byte)0x10, (byte)0x1C, (byte)0xFF, (byte)0xFC, (byte)0xAD, (byte)0x88, (byte)0x75, (byte)0x74, (byte)0x8A, (byte)0x82};
+ Object[] arrayMix = { "0x00", 8, "16", "0x1C", 255, (byte)0xFC, 173, 136, 117, 116, -118, "-126"};
+
+ String expected = new String(arrayBytes);
+ ArrayList listBytes = new ArrayList<>(arrayBytes.length);
+ for (Byte element : arrayBytes) {
+ listBytes.add(element);
+ }
+ Assert.assertEquals(expected, TbUtils.bytesToString(listBytes));
+
+ ArrayList