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/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
index f64ca1da00..97b22b5706 100644
--- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
@@ -226,7 +226,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(
@@ -258,7 +258,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/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 0fe1d14cc7..a85ef5e5b1 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
@@ -347,6 +347,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/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/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/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..c2dd66dde0 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);
+
+}
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..e35c86e070 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,17 @@ 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));
+ Optional optional = Optional.ofNullable(FSTUtils.decode(byteArray));
+ optional.ifPresent(obj -> fstStatsService.incrementDecode(obj.getClass()));
+ return optional;
} catch (IllegalArgumentException e) {
log.error("Error during deserialization message, [{}]", e.getMessage());
return Optional.empty();
@@ -41,7 +48,9 @@ public class ProtoWithFSTService implements DataDecodingEncodingService {
@Override
public byte[] encode(T msq) {
- return FSTUtils.encode(msq);
+ var bytes = FSTUtils.encode(msq);
+ fstStatsService.incrementEncode(msq.getClass());
+ return bytes;
}
diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/FstStatsServiceImpl.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/FstStatsServiceImpl.java
new file mode 100644
index 0000000000..5d78b8c36a
--- /dev/null
+++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/FstStatsServiceImpl.java
@@ -0,0 +1,42 @@
+/**
+ * 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.stats;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.FstStatsService;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class FstStatsServiceImpl implements FstStatsService {
+ private final ConcurrentHashMap encodeCounters = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap decodeCounters = new ConcurrentHashMap<>();
+
+ @Autowired
+ private StatsFactory statsFactory;
+
+ @Override
+ public void incrementEncode(Class> clazz) {
+ encodeCounters.computeIfAbsent(clazz.getSimpleName(), key -> statsFactory.createStatsCounter("fstEncode", key)).increment();
+ }
+
+ @Override
+ public void incrementDecode(Class> clazz) {
+ decodeCounters.computeIfAbsent(clazz.getSimpleName(), key -> statsFactory.createStatsCounter("fstDecode", key)).increment();
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java
index 0e62670f81..0176b14d54 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java
@@ -21,4 +21,8 @@ public interface MonitoringTarget {
UUID getDeviceId();
+ String getBaseUrl();
+
+ boolean isCheckDomainIps();
+
}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java
index 77d702f779..c5f843192c 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java
@@ -23,9 +23,8 @@ import java.util.List;
@Data
public abstract class TransportMonitoringConfig implements MonitoringConfig {
- private int requestTimeoutMs;
-
private List targets;
+ private int requestTimeoutMs;
public abstract TransportType getTransportType();
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java
index 816f64fbce..e3277af0ff 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java
@@ -25,6 +25,7 @@ public class TransportMonitoringTarget implements MonitoringTarget {
private String baseUrl;
private DeviceConfig device; // set manually during initialization
+ private boolean checkDomainIps;
@Override
public UUID getDeviceId() {
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java
index 3370d42462..52e73a64cd 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java
@@ -25,4 +25,8 @@ public class Latencies {
return String.format("%sRequest", key);
}
+ public static String wsUpdate(String key) {
+ return String.format("%sWsUpdate", key);
+ }
+
}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java
index c64291e30f..4b17bf179f 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java
@@ -15,53 +15,15 @@
*/
package org.thingsboard.monitoring.data;
-import com.google.common.util.concurrent.AtomicDouble;
-import lombok.RequiredArgsConstructor;
+import lombok.Data;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@RequiredArgsConstructor
+@Data(staticConstructor = "of")
public class Latency {
-
private final String key;
- private final AtomicDouble latencySum = new AtomicDouble();
- private final AtomicInteger counter = new AtomicInteger();
-
- public synchronized void report(double latencyInMs) {
- latencySum.addAndGet(latencyInMs);
- counter.incrementAndGet();
- }
-
- public synchronized double getAvg() {
- return latencySum.get() / counter.get();
- }
-
- public boolean isNotEmpty() {
- return counter.get() > 0;
- }
-
- public synchronized void reset() {
- latencySum.set(0.0);
- counter.set(0);
- }
-
- public String getKey() {
- return key;
- }
-
- public synchronized Latency snapshot() {
- Latency snapshot = new Latency(key);
- snapshot.latencySum.set(latencySum.get());
- snapshot.counter.set(counter.get());
- return snapshot;
- }
+ private final double value;
- @Override
- public String toString() {
- return "Latency{" +
- "key='" + key + '\'' +
- ", avgLatency=" + getAvg() +
- '}';
+ public String getFormattedValue() {
+ return String.format("%,.2f ms", value);
}
}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
index 939cb7440f..5744e40d41 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
@@ -21,11 +21,11 @@ import java.util.Collection;
public class HighLatencyNotification implements Notification {
- private final Collection latencies;
+ private final Collection highLatencies;
private final int thresholdMs;
- public HighLatencyNotification(Collection latencies, int thresholdMs) {
- this.latencies = latencies;
+ public HighLatencyNotification(Collection highLatencies, int thresholdMs) {
+ this.highLatencies = highLatencies;
this.thresholdMs = thresholdMs;
}
@@ -33,8 +33,8 @@ public class HighLatencyNotification implements Notification {
public String getText() {
StringBuilder text = new StringBuilder();
text.append("Some of the latencies are higher than ").append(thresholdMs).append(" ms:\n");
- latencies.forEach(latency -> {
- text.append(String.format("[%s] %,.2f ms\n", latency.getKey(), latency.getAvg()));
+ highLatencies.forEach(latency -> {
+ text.append(String.format("[%s] %s\n", latency.getKey(), latency.getFormattedValue()));
});
return text.toString();
}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java
index affca1ce09..290fd307c6 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.monitoring.service;
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,13 +31,17 @@ import org.thingsboard.monitoring.util.TbStopWatch;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
@Slf4j
public abstract class BaseHealthChecker {
+ @Getter
protected final C config;
+ @Getter
protected final T target;
private Object info;
@@ -48,6 +53,9 @@ public abstract class BaseHealthChecker> associates = new HashMap<>();
+
public static final String TEST_TELEMETRY_KEY = "testData";
@PostConstruct
@@ -84,6 +92,10 @@ public abstract class BaseHealthChecker {
+ healthChecker.check(wsClient);
+ });
}
private void checkWsUpdate(WsClient wsClient, String testValue) {
@@ -96,10 +108,9 @@ public abstract class BaseHealthChecker, T extends MonitoringTarget> {
- @Autowired
+ @Autowired(required = false)
private List configs;
private final List> healthCheckers = new LinkedList<>();
private final List devices = new LinkedList<>();
@@ -54,18 +63,32 @@ public abstract class BaseMonitoringService, T ext
@PostConstruct
private void init() {
+ if (configs == null || configs.isEmpty()) {
+ return;
+ }
tbClient.logIn();
configs.forEach(config -> {
config.getTargets().forEach(target -> {
- BaseHealthChecker healthChecker = (BaseHealthChecker) createHealthChecker(config, target);
- log.info("Initializing {}", healthChecker.getClass().getSimpleName());
- healthChecker.initialize(tbClient);
- devices.add(target.getDeviceId());
+ BaseHealthChecker healthChecker = initHealthChecker(target, config);
healthCheckers.add(healthChecker);
+
+ if (target.isCheckDomainIps()) {
+ getAssociatedUrls(target.getBaseUrl()).forEach(url -> {
+ healthChecker.getAssociates().put(url, initHealthChecker(createTarget(url), config));
+ });
+ }
});
});
}
+ private BaseHealthChecker initHealthChecker(T target, C config) {
+ BaseHealthChecker healthChecker = (BaseHealthChecker) createHealthChecker(config, target);
+ log.info("Initializing {} for {}", healthChecker.getClass().getSimpleName(), target.getBaseUrl());
+ healthChecker.initialize(tbClient);
+ devices.add(target.getDeviceId());
+ return healthChecker;
+ }
+
public final void runChecks() {
if (healthCheckers.isEmpty()) {
return;
@@ -78,9 +101,8 @@ public abstract class BaseMonitoringService, T ext
try (WsClient wsClient = wsClientFactory.createClient(accessToken)) {
wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply();
-
for (BaseHealthChecker healthChecker : healthCheckers) {
- healthChecker.check(wsClient);
+ check(healthChecker, wsClient);
}
}
reporter.reportLatencies(tbClient);
@@ -94,8 +116,61 @@ public abstract class BaseMonitoringService, T ext
}
}
+ private void check(BaseHealthChecker healthChecker, WsClient wsClient) throws Exception {
+ healthChecker.check(wsClient);
+
+ T target = healthChecker.getTarget();
+ if (target.isCheckDomainIps()) {
+ Set associatedUrls = getAssociatedUrls(target.getBaseUrl());
+ Map> associates = healthChecker.getAssociates();
+ Set prevAssociatedUrls = new HashSet<>(associates.keySet());
+
+ boolean changed = false;
+ for (String url : associatedUrls) {
+ if (!prevAssociatedUrls.contains(url)) {
+ BaseHealthChecker associate = initHealthChecker(createTarget(url), healthChecker.getConfig());
+ associates.put(url, associate);
+ changed = true;
+ }
+ }
+ for (String url : prevAssociatedUrls) {
+ if (!associatedUrls.contains(url)) {
+ stopHealthChecker(healthChecker);
+ associates.remove(url);
+ changed = true;
+ }
+ }
+ if (changed) {
+ log.info("Updated IPs for {}: {} (old list: {})", target.getBaseUrl(), associatedUrls, prevAssociatedUrls);
+ }
+ }
+ }
+
+ @SneakyThrows
+ private Set getAssociatedUrls(String baseUrl) {
+ URI url = new URI(baseUrl);
+ return Arrays.stream(InetAddress.getAllByName(url.getHost()))
+ .map(InetAddress::getHostAddress)
+ .map(ip -> {
+ try {
+ return new URI(url.getScheme(), null, ip, url.getPort(), "", null, null).toString();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .collect(Collectors.toSet());
+ }
+
+ private void stopHealthChecker(BaseHealthChecker healthChecker) throws Exception {
+ healthChecker.destroyClient();
+ devices.remove(healthChecker.getTarget().getDeviceId());
+ log.info("Stopped {} for {}", healthChecker.getClass().getSimpleName(), healthChecker.getTarget().getBaseUrl());
+ }
+
protected abstract BaseHealthChecker, ?> createHealthChecker(C config, T target);
+ protected abstract T createTarget(String baseUrl);
+
protected abstract String getName();
}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java
index f41454a76a..aabf04caee 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java
@@ -63,25 +63,20 @@ public class MonitoringReporter {
private String reportingAssetId;
public void reportLatencies(TbClient tbClient) {
- List latencies = this.latencies.values().stream()
- .filter(Latency::isNotEmpty)
- .map(latency -> {
- Latency snapshot = latency.snapshot();
- latency.reset();
- return snapshot;
- })
- .collect(Collectors.toList());
if (latencies.isEmpty()) {
return;
}
- log.info("Latencies:\n{}", latencies.stream().map(latency -> latency.getKey() + ": " + latency.getAvg() + " ms")
+ log.debug("Latencies:\n{}", latencies.values().stream().map(latency -> latency.getKey() + ": " + latency.getFormattedValue())
.collect(Collectors.joining("\n")) + "\n");
-
if (!latencyReportingEnabled) return;
- if (latencies.stream().anyMatch(latency -> latency.getAvg() >= (double) latencyThresholdMs)) {
- HighLatencyNotification highLatencyNotification = new HighLatencyNotification(latencies, latencyThresholdMs);
+ List highLatencies = latencies.values().stream()
+ .filter(latency -> latency.getValue() >= (double) latencyThresholdMs)
+ .collect(Collectors.toList());
+ if (!highLatencies.isEmpty()) {
+ HighLatencyNotification highLatencyNotification = new HighLatencyNotification(highLatencies, latencyThresholdMs);
notificationService.sendNotification(highLatencyNotification);
+ log.warn("{}", highLatencyNotification.getText());
}
try {
@@ -99,10 +94,11 @@ public class MonitoringReporter {
}
ObjectNode msg = JacksonUtil.newObjectNode();
- latencies.forEach(latency -> {
- msg.set(latency.getKey(), new DoubleNode(latency.getAvg()));
+ latencies.values().forEach(latency -> {
+ msg.set(latency.getKey(), new DoubleNode(latency.getValue()));
});
tbClient.saveEntityTelemetry(new AssetId(UUID.fromString(reportingAssetId)), "time", msg);
+ latencies.clear();
} catch (Exception e) {
log.error("Failed to report latencies: {}", e.getMessage());
}
@@ -112,7 +108,7 @@ public class MonitoringReporter {
String latencyKey = key + "Latency";
double latencyInMs = (double) latencyInNanos / 1000_000;
log.trace("Reporting latency [{}]: {} ms", key, latencyInMs);
- latencies.computeIfAbsent(latencyKey, k -> new Latency(latencyKey)).report(latencyInMs);
+ latencies.put(latencyKey, Latency.of(latencyKey, latencyInMs));
}
public void serviceFailure(Object serviceKey, Throwable error) {
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java
index b3ce86e799..ca7c5a94da 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java
@@ -33,6 +33,13 @@ public final class TransportsMonitoringService extends BaseMonitoringService 20;"
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": "",
+ "layoutX": 427,
+ "layoutY": 541
+ },
+ "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
+ "name": "Test TBEL script",
+ "debugMode": false,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "jsScript": "return msg.temperature > 20;",
+ "tbelScript": "var a = \"a\";\nvar b = \"b\";\nreturn a.equals(\"a\") && b.equals(\"b\");"
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": "",
+ "layoutX": 40,
+ "layoutY": 252
+ },
+ "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
+ "name": "Add arrival timestamp",
+ "debugMode": false,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};",
+ "tbelScript": "metadata.arrivalTs = Date.now();\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": "",
+ "layoutX": 1467,
+ "layoutY": 267
+ },
+ "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
+ "name": "Calculate additional latencies",
+ "debugMode": true,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "scriptLang": "TBEL",
+ "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};",
+ "tbelScript": "var arrivalLatency = metadata.arrivalTs - metadata.ts;\nvar processingTime = Date.now() - metadata.arrivalTs;\nmsg = {\n arrivalLatency: arrivalLatency,\n processingTime: processingTime\n};\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": "",
+ "layoutX": 1438,
+ "layoutY": 403
+ },
+ "type": "org.thingsboard.rule.engine.transform.TbChangeOriginatorNode",
+ "name": "To latencies asset",
+ "debugMode": false,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "originatorSource": "ENTITY",
+ "entityType": "ASSET",
+ "entityNamePattern": "[Monitoring] Latencies",
+ "relationsQuery": {
+ "direction": "FROM",
+ "maxLevel": 1,
+ "filters": [
+ {
+ "relationType": "Contains",
+ "entityTypes": []
+ }
+ ],
+ "fetchLastLevelOnly": false
+ }
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": null,
+ "layoutX": 1458,
+ "layoutY": 505
+ },
+ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
+ "name": "Save Timeseries",
+ "debugMode": true,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "defaultTTL": 0
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": "",
+ "layoutX": 928,
+ "layoutY": 266
+ },
+ "type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode",
+ "name": "Has testData",
+ "debugMode": false,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "messageNames": [
+ "testData"
+ ],
+ "metadataNames": [],
+ "checkAllKeys": true
+ },
+ "externalId": null
+ },
+ {
+ "additionalInfo": {
+ "description": null,
+ "layoutX": 1203,
+ "layoutY": 327
+ },
+ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
+ "name": "Save Timeseries with TTL",
+ "debugMode": true,
+ "singletonMode": false,
+ "configurationVersion": 0,
+ "configuration": {
+ "defaultTTL": 180,
+ "skipLatestPersistence": null,
+ "useServerTs": null
+ },
+ "externalId": null
+ }
+ ],
+ "connections": [
+ {
+ "fromIndex": 2,
+ "toIndex": 1,
+ "type": "Post attributes"
+ },
+ {
+ "fromIndex": 2,
+ "toIndex": 3,
+ "type": "RPC Request from Device"
+ },
+ {
+ "fromIndex": 2,
+ "toIndex": 4,
+ "type": "Other"
+ },
+ {
+ "fromIndex": 2,
+ "toIndex": 5,
+ "type": "RPC Request to Device"
+ },
+ {
+ "fromIndex": 2,
+ "toIndex": 16,
+ "type": "Post telemetry"
+ },
+ {
+ "fromIndex": 6,
+ "toIndex": 2,
+ "type": "False"
+ },
+ {
+ "fromIndex": 6,
+ "toIndex": 7,
+ "type": "True"
+ },
+ {
+ "fromIndex": 7,
+ "toIndex": 2,
+ "type": "False"
+ },
+ {
+ "fromIndex": 7,
+ "toIndex": 8,
+ "type": "True"
+ },
+ {
+ "fromIndex": 8,
+ "toIndex": 2,
+ "type": "Success"
+ },
+ {
+ "fromIndex": 9,
+ "toIndex": 10,
+ "type": "Success"
+ },
+ {
+ "fromIndex": 10,
+ "toIndex": 11,
+ "type": "True"
+ },
+ {
+ "fromIndex": 11,
+ "toIndex": 6,
+ "type": "True"
+ },
+ {
+ "fromIndex": 12,
+ "toIndex": 9,
+ "type": "Success"
+ },
+ {
+ "fromIndex": 13,
+ "toIndex": 14,
+ "type": "Success"
+ },
+ {
+ "fromIndex": 14,
+ "toIndex": 15,
+ "type": "Success"
+ },
+ {
+ "fromIndex": 16,
+ "toIndex": 0,
+ "type": "False"
+ },
+ {
+ "fromIndex": 16,
+ "toIndex": 17,
+ "type": "True"
+ },
+ {
+ "fromIndex": 17,
+ "toIndex": 13,
+ "type": "Success"
+ }
+ ],
+ "ruleChainConnections": null
+ }
+}
\ No newline at end of file
diff --git a/monitoring/src/main/resources/tb-monitoring.yml b/monitoring/src/main/resources/tb-monitoring.yml
index 88979bff1a..57b9b39ec0 100644
--- a/monitoring/src/main/resources/tb-monitoring.yml
+++ b/monitoring/src/main/resources/tb-monitoring.yml
@@ -51,8 +51,10 @@ monitoring:
# MQTT QoS
qos: '${MQTT_QOS_LEVEL:1}'
targets:
- # MQTT transport base url, tcp://DOMAIN:1883 by default
+ # MQTT transport base url, tcp://DOMAIN:1883 by default
- base_url: '${MQTT_TRANSPORT_BASE_URL:tcp://${monitoring.domain}:1883}'
+ # Whether to monitor IPs associated with the domain from base url
+ check_domain_ips: '${MQTT_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables:
# monitoring.transports.mqtt.targets[1].base_url, monitoring.transports.mqtt.targets[2].base_url, etc.
@@ -62,8 +64,10 @@ monitoring:
# CoAP request timeout in milliseconds
request_timeout_ms: '${COAP_REQUEST_TIMEOUT_MS:4000}'
targets:
- # CoAP transport base url, coap://DOMAIN by default
+ # CoAP transport base url, coap://DOMAIN by default
- base_url: '${COAP_TRANSPORT_BASE_URL:coap://${monitoring.domain}}'
+ # Whether to monitor IPs associated with the domain from base url
+ check_domain_ips: '${COAP_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables:
# monitoring.transports.coap.targets[1].base_url, monitoring.transports.coap.targets[2].base_url, etc.
@@ -73,8 +77,10 @@ monitoring:
# HTTP request timeout in milliseconds
request_timeout_ms: '${HTTP_REQUEST_TIMEOUT_MS:4000}'
targets:
- # HTTP transport base url, http://DOMAIN by default
+ # HTTP transport base url, http://DOMAIN by default
- base_url: '${HTTP_TRANSPORT_BASE_URL:http://${monitoring.domain}}'
+ # Whether to monitor IPs associated with the domain from base url
+ check_domain_ips: '${HTTP_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables:
# monitoring.transports.http.targets[1].base_url, monitoring.transports.http.targets[2].base_url, etc.
@@ -84,8 +90,10 @@ monitoring:
# LwM2M request timeout in milliseconds
request_timeout_ms: '${LWM2M_REQUEST_TIMEOUT_MS:4000}'
targets:
- # LwM2M transport base url, coap://DOMAIN:5685 by default
+ # LwM2M transport base url, coap://DOMAIN:5685 by default
- base_url: '${LWM2M_TRANSPORT_BASE_URL:coap://${monitoring.domain}:5685}'
+ # Whether to monitor IPs associated with the domain from base url
+ check_domain_ips: '${LWM2M_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables:
# monitoring.transports.lwm2m.targets[1].base_url, monitoring.transports.lwm2m.targets[2].base_url, etc.
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java
index fe467ec341..7d87351bef 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java
@@ -253,12 +253,16 @@ public class ThingsBoardDbInstaller {
.add(tbVcExecutorLogVolume)
.add(resolveRedisComposeVolumeLog());
+ if (IS_HYBRID_MODE) {
+ rmVolumesCommand.add(cassandraDataVolume);
+ }
+
dockerCompose.withCommand(rmVolumesCommand.toString());
}
private String resolveRedisComposeVolumeLog() {
if (IS_REDIS_CLUSTER) {
- return IntStream.range(0, 6).mapToObj(i -> redisClusterDataVolume + "-" + i).collect(Collectors.joining());
+ return IntStream.range(0, 6).mapToObj(i -> " " + redisClusterDataVolume + "-" + i).collect(Collectors.joining());
}
if (IS_REDIS_SENTINEL) {
return redisSentinelDataVolume + "-" + "master " + " " +
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
index 5a7e9b631d..6ad50196b3 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
@@ -26,6 +26,7 @@ import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
+import org.awaitility.Awaitility;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -337,8 +338,12 @@ public class MqttClientTest extends AbstractContainerTest {
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
testRestClient.deleteDeviceIfExists(device.getId());
- TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
- assertThat(mqttClient.isConnected()).isFalse();
+
+ Awaitility
+ .await()
+ .alias("Check device connection.")
+ .atMost(10, TimeUnit.SECONDS)
+ .until(() -> !mqttClient.isConnected());
}
@Test