diff --git a/application/pom.xml b/application/pom.xml index 902b541566..da2c0bc249 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard application diff --git a/application/src/main/data/json/edge/instructions/install/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md index 22bb3f63a0..bcc1967dfd 100644 --- a/application/src/main/data/json/edge/instructions/install/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -8,15 +8,15 @@ sudo yum install -y nano wget sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm ``` -#### Install Java 11 (OpenJDK) -ThingsBoard service is running on Java 11. Follow these instructions to install OpenJDK 11: +#### Install Java 17 (OpenJDK) +ThingsBoard service is running on Java 17. Follow these instructions to install OpenJDK 17: ```bash -sudo yum install java-11-openjdk +sudo yum install java-17-openjdk {:copy-code} ``` -Please don't forget to configure your operating system to use OpenJDK 11 by default. +Please don't forget to configure your operating system to use OpenJDK 17 by default. You can configure which version is the default using the following command: ```bash @@ -34,7 +34,7 @@ java -version Expected command output is: ```text -openjdk version "11.0.xx" +openjdk version "17.x.xx" OpenJDK Runtime Environment (...) OpenJDK 64-Bit Server VM (build ...) ``` @@ -144,14 +144,14 @@ CREATE DATABASE tb_edge; Download installation package: ```bash -wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_VERSION}/tb-edge-${TB_EDGE_VERSION}.rpm +wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.rpm {:copy-code} ``` Go to the download repository and install ThingsBoard Edge service: ```bash -sudo rpm -Uvh tb-edge-${TB_EDGE_VERSION}.rpm +sudo rpm -Uvh tb-edge-${TB_EDGE_TAG}.rpm {:copy-code} ``` diff --git a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index 6fcdcd50f2..fe9601443f 100644 --- a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -1,15 +1,15 @@ Here is the list of commands, that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server. -#### Install Java 11 (OpenJDK) -ThingsBoard service is running on Java 11. Follow these instructions to install OpenJDK 11: +#### Install Java 17 (OpenJDK) +ThingsBoard service is running on Java 17. Follow these instructions to install OpenJDK 17: ```bash sudo apt update -sudo apt install openjdk-11-jdk +sudo apt install openjdk-17-jdk {:copy-code} ``` -Please don't forget to configure your operating system to use OpenJDK 11 by default. +Please don't forget to configure your operating system to use OpenJDK 17 by default. You can configure which version is the default using the following command: ```bash @@ -27,7 +27,7 @@ java -version Expected command output is: ```text -openjdk version "11.0.xx" +openjdk version "17.x.xx" OpenJDK Runtime Environment (...) OpenJDK 64-Bit Server VM (build ...) ``` @@ -76,14 +76,14 @@ CREATE DATABASE tb_edge; Download installation package: ```bash -wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_VERSION}/tb-edge-${TB_EDGE_VERSION}.deb +wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.deb {:copy-code} ``` Go to the download repository and install ThingsBoard Edge service: ```bash -sudo dpkg -i tb-edge-${TB_EDGE_VERSION}.deb +sudo dpkg -i tb-edge-${TB_EDGE_TAG}.deb {:copy-code} ``` diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 42c4f199b5..9648783b1c 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -248,7 +248,7 @@ "type": "customPretty", "customHtml": "
\r\n \r\n

Add gateway

\r\n \r\n \r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n \r\n Name\r\n \r\n \r\n Gateway name is required.\r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n", "customCss": ".add-entity-form {\r\n min-width: 400px !important;\r\n}\r\n\r\n.add-entity-form .boolean-value-input {\r\n padding-left: 5px;\r\n}\r\n\r\n.add-entity-form .boolean-value-input .checkbox-label {\r\n margin-bottom: 8px;\r\n color: rgba(0,0,0,0.54);\r\n font-size: 12px;\r\n}\r\n\r\n.relations-list .header {\r\n padding-right: 5px;\r\n padding-bottom: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .header .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n font-size: 12px;\r\n font-weight: 700;\r\n color: rgba(0, 0, 0, .54);\r\n white-space: nowrap;\r\n}\r\n\r\n.relations-list .mat-form-field-infix {\r\n width: auto !important;\r\n}\r\n\r\n.relations-list .body {\r\n padding-right: 5px;\r\n padding-bottom: 15px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .row {\r\n padding-top: 5px;\r\n}\r\n\r\n.relations-list .body .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .md-button {\r\n margin: 0;\r\n}\r\n\r\n", - "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n goToConfigState();\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n", + "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Launch command\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n goToConfigState();\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n", "customResources": [], "openInSeparateDialog": false, "openInPopover": false, @@ -2134,7 +2134,7 @@ "padding": "0px", "settings": { "useMarkdownTextFunction": false, - "markdownTextPattern": "
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n", + "markdownTextPattern": "
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n", "applyDefaultMarkdownStyle": false, "markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}" }, @@ -2996,172 +2996,6 @@ "id": "bb27723a-989c-2327-5808-b56d490b93ab", "typeFullFqn": "system.cards.entities_table" }, - "e3a9539d-2ccb-96e0-2afa-36f93e40233d": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 6.5, - "config": { - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 86400000, - "quickInterval": "CURRENT_DAY" - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1684327643501, - "endTimeMs": 1684414043501 - }, - "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": { - "entitiesTitle": "Devices", - "enableSearch": true, - "enableSelectColumnDisplay": true, - "enableStickyHeader": true, - "enableStickyAction": true, - "reserveSpaceForHiddenAction": "true", - "displayEntityName": true, - "entityNameColumnTitle": "Device Name", - "displayEntityLabel": false, - "displayEntityType": false, - "displayPagination": true, - "defaultPageSize": 10, - "defaultSortOrder": "entityName", - "useRowStyleFunction": false - }, - "title": "Devices", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400, - "padding": "5px 10px 5px 10px" - }, - "useDashboardTimewindow": false, - "showLegend": false, - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4", - "filterId": "3931abd5-2205-9386-6ea9-8e8a8131bb9d", - "dataKeys": [ - { - "name": "type", - "type": "entityField", - "label": "Device Type", - "color": "#2196f3", - "settings": {}, - "_hash": 0.3129929097366162, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "active", - "type": "attribute", - "label": "Status", - "color": "#4caf50", - "settings": { - "columnWidth": "0px", - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "let cssClass;\r\nswitch (value) {\r\n case \"Active\":\r\n cssClass = \"status status-active\";\r\n break;\r\n case \"Inactive\":\r\n default:\r\n cssClass = \"status status-inactive\";\r\n break;\r\n }\r\n \r\n return `${value}`;", - "defaultColumnVisibility": "visible", - "columnSelectionToDisplay": "enabled" - }, - "_hash": 0.5969880627410065, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value == 'true' ? \"Active\": \"Inactive\";" - }, - { - "name": "connectorName", - "type": "attribute", - "label": "Connector Name", - "color": "#f44336", - "settings": {}, - "_hash": 0.012483045440007778, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "connectorType", - "type": "attribute", - "label": "Connector Type", - "color": "#ffc107", - "settings": {}, - "_hash": 0.6004192233378134, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ], - "alarmFilterConfig": { - "statusList": [ - "ACTIVE" - ] - } - } - ], - "displayTimewindow": true, - "showTitleIcon": false, - "titleTooltip": "", - "widgetStyle": {}, - "widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n", - "pageSize": 1024, - "noDataDisplayMessage": "", - "enableDataExport": false, - "actions": { - "actionCellButton": [ - { - "name": "Show Device Info", - "icon": "info", - "useShowWidgetActionFunction": null, - "showWidgetActionFunction": "return true;", - "type": "custom", - "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", - "openInSeparateDialog": false, - "openInPopover": false, - "id": "94de7690-f91d-b032-6771-85af99abd749" - } - ] - } - }, - "row": 0, - "col": 0, - "id": "e3a9539d-2ccb-96e0-2afa-36f93e40233d", - "typeFullFqn": "system.cards.entities_table" - }, "cf2eba6b-44f6-9cc2-6089-35c735f54898": { "type": "latest", "sizeX": 7.5, @@ -5632,33 +5466,6 @@ } } }, - "gateway_devices_5": { - "name": "gateway_devices_opcua_async", - "root": false, - "layouts": { - "main": { - "widgets": { - "e3a9539d-2ccb-96e0-2afa-36f93e40233d": { - "sizeX": 24, - "sizeY": 11, - "row": 0, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "columns": 24, - "margin": 0, - "outerMargin": true, - "backgroundSizeMode": "100%", - "autoFillHeight": false, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70 - } - } - } - }, "gateway_devices_6": { "name": "gateway_devices_ble", "root": false, diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index ad1ed7824a..007f77aa20 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -118,8 +118,8 @@ public class TenantProfileController extends BaseController { "Let's review the example of tenant profile data below: " + "\n\n" + MARKDOWN_CODE_BLOCK_START + "{\n" + - " \"name\": \"Default\",\n" + - " \"description\": \"Default tenant profile\",\n" + + " \"name\": \"Your name\",\n" + + " \"description\": \"Your description\",\n" + " \"isolatedTbRuleEngine\": false,\n" + " \"profileData\": {\n" + " \"configuration\": {\n" + @@ -162,7 +162,7 @@ public class TenantProfileController extends BaseController { " \"warnThreshold\": 0\n" + " }\n" + " },\n" + - " \"default\": true\n" + + " \"default\": false\n" + "}" + MARKDOWN_CODE_BLOCK_END + "Remove 'id', from the request body example (below) to create new Tenant Profile entity." + diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java new file mode 100644 index 0000000000..c82f7babc5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.instructions; + +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.service.install.InstallScripts; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +@RequiredArgsConstructor +public abstract class BaseEdgeInstallUpgradeInstructionsService { + + private static final String EDGE_DIR = "edge"; + private static final String INSTRUCTIONS_DIR = "instructions"; + + private final InstallScripts installScripts; + + @Value("${app.version:unknown}") + @Setter + protected String appVersion; + + protected String readFile(Path file) { + try { + return Files.readString(file); + } catch (IOException e) { + log.warn("Failed to read file: {}", file, e); + throw new RuntimeException(e); + } + } + + protected String getTagVersion(String version) { + return version.endsWith(".0") ? version.substring(0, version.length() - 2) : version; + } + + protected Path resolveFile(String subDir, String... subDirs) { + return getEdgeInstructionsDir().resolve(Paths.get(subDir, subDirs)); + } + + protected Path getEdgeInstructionsDir() { + return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, getBaseDirName()); + } + + protected abstract String getBaseDirName(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java index fd0d9fc2dd..64f96aa4e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.service.edge.instructions; -import lombok.RequiredArgsConstructor; -import lombok.Setter; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -27,47 +26,32 @@ import org.thingsboard.server.dao.util.DeviceConnectivityUtil; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.install.InstallScripts; -import jakarta.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - @Service @Slf4j -@RequiredArgsConstructor @ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") @TbCoreComponent -public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstructionsService { +public class DefaultEdgeInstallInstructionsService extends BaseEdgeInstallUpgradeInstructionsService implements EdgeInstallInstructionsService { - private static final String EDGE_DIR = "edge"; - private static final String INSTRUCTIONS_DIR = "instructions"; private static final String INSTALL_DIR = "install"; - private final InstallScripts installScripts; - @Value("${edges.rpc.port}") private int rpcPort; @Value("${edges.rpc.ssl.enabled}") private boolean sslEnabled; - @Value("${app.version:unknown}") - @Setter - private String appVersion; + public DefaultEdgeInstallInstructionsService(InstallScripts installScripts) { + super(installScripts); + } @Override public EdgeInstructions getInstallInstructions(Edge edge, String installationMethod, HttpServletRequest request) { - switch (installationMethod.toLowerCase()) { - case "docker": - return getDockerInstallInstructions(edge, request); - case "ubuntu": - return getUbuntuInstallInstructions(edge, request); - case "centos": - return getCentosInstallInstructions(edge, request); - default: - throw new IllegalArgumentException("Unsupported installation method for Edge: " + installationMethod); - } + return switch (installationMethod.toLowerCase()) { + case "docker" -> getDockerInstallInstructions(edge, request); + case "ubuntu", "centos" -> getLinuxInstallInstructions(edge, request, installationMethod.toLowerCase()); + default -> + throw new IllegalArgumentException("Unsupported installation method for Edge: " + installationMethod); + }; } private EdgeInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) { @@ -88,25 +72,16 @@ public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstruc return new EdgeInstructions(dockerInstallInstructions); } - private EdgeInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) { - String ubuntuInstallInstructions = readFile(resolveFile("ubuntu", "instructions.md")); + private EdgeInstructions getLinuxInstallInstructions(Edge edge, HttpServletRequest request, String os) { + String ubuntuInstallInstructions = readFile(resolveFile(os, "instructions.md")); ubuntuInstallInstructions = replacePlaceholders(ubuntuInstallInstructions, edge); ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${BASE_URL}", request.getServerName()); String edgeVersion = appVersion.replace("-SNAPSHOT", ""); ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); + ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${TB_EDGE_TAG}", getTagVersion(edgeVersion)); return new EdgeInstructions(ubuntuInstallInstructions); } - - private EdgeInstructions getCentosInstallInstructions(Edge edge, HttpServletRequest request) { - String centosInstallInstructions = readFile(resolveFile("centos", "instructions.md")); - centosInstallInstructions = replacePlaceholders(centosInstallInstructions, edge); - centosInstallInstructions = centosInstallInstructions.replace("${BASE_URL}", request.getServerName()); - String edgeVersion = appVersion.replace("-SNAPSHOT", ""); - centosInstallInstructions = centosInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); - return new EdgeInstructions(centosInstallInstructions); - } - private String replacePlaceholders(String instructions, Edge edge) { instructions = instructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey()); instructions = instructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret()); @@ -115,20 +90,8 @@ public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstruc return instructions; } - private String readFile(Path file) { - try { - return Files.readString(file); - } catch (IOException e) { - log.warn("Failed to read file: {}", file, e); - throw new RuntimeException(e); - } - } - - private Path resolveFile(String subDir, String... subDirs) { - return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs)); - } - - private Path getEdgeInstallInstructionsDir() { - return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, INSTALL_DIR); + @Override + protected String getBaseDirName() { + return INSTALL_DIR; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java index 3ff8ae784a..7113123a45 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge.instructions; -import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -32,47 +31,37 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.install.InstallScripts; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Service @Slf4j -@RequiredArgsConstructor @ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") @TbCoreComponent -public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstructionsService { +public class DefaultEdgeUpgradeInstructionsService extends BaseEdgeInstallUpgradeInstructionsService implements EdgeUpgradeInstructionsService { private static final Map upgradeVersionHashMap = new HashMap<>(); - private static final String EDGE_DIR = "edge"; - private static final String INSTRUCTIONS_DIR = "instructions"; private static final String UPGRADE_DIR = "upgrade"; - private final InstallScripts installScripts; private final AttributesService attributesService; - @Value("${app.version:unknown}") - @Setter - private String appVersion; + public DefaultEdgeUpgradeInstructionsService(AttributesService attributesService, InstallScripts installScripts) { + super(installScripts); + this.attributesService = attributesService; + } @Override public EdgeInstructions getUpgradeInstructions(String edgeVersion, String upgradeMethod) { String tbVersion = appVersion.replace("-SNAPSHOT", ""); String currentEdgeVersion = convertEdgeVersionToDocsFormat(edgeVersion); - switch (upgradeMethod.toLowerCase()) { - case "docker": - return getDockerUpgradeInstructions(tbVersion, currentEdgeVersion); - case "ubuntu": - case "centos": - return getLinuxUpgradeInstructions(tbVersion, currentEdgeVersion, upgradeMethod.toLowerCase()); - default: - throw new IllegalArgumentException("Unsupported upgrade method for Edge: " + upgradeMethod); - } + return switch (upgradeMethod.toLowerCase()) { + case "docker" -> getDockerUpgradeInstructions(tbVersion, currentEdgeVersion); + case "ubuntu", "centos" -> + getLinuxUpgradeInstructions(tbVersion, currentEdgeVersion, upgradeMethod.toLowerCase()); + default -> throw new IllegalArgumentException("Unsupported upgrade method for Edge: " + upgradeMethod); + }; } @Override @@ -167,28 +156,12 @@ public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstruc return new EdgeInstructions(result.toString()); } - private String getTagVersion(String version) { - return version.endsWith(".0") ? version.substring(0, version.length() - 2) : version; - } - private String convertEdgeVersionToDocsFormat(String edgeVersion) { return edgeVersion.replace("_", ".").substring(2); } - private String readFile(Path file) { - try { - return Files.readString(file); - } catch (IOException e) { - log.warn("Failed to read file: {}", file, e); - throw new RuntimeException(e); - } - } - - private Path resolveFile(String subDir, String... subDirs) { - return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs)); - } - - private Path getEdgeInstallInstructionsDir() { - return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, UPGRADE_DIR); + @Override + protected String getBaseDirName() { + return UPGRADE_DIR; } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 8abb78d9c1..9367439d4f 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.subscription; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; @@ -24,9 +26,9 @@ import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.AttributeScope; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; @@ -51,8 +53,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscript import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -143,7 +143,28 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer * Even if we cache locally the list of active subscriptions by entity id, it is still time-consuming operation to get them from cache * Since number of subscriptions is usually much less than number of devices that are pushing data. */ - subscriptionsByEntityId.values().forEach(sub -> pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED))); + Set staleSubs = new HashSet<>(); + subscriptionsByEntityId.forEach((id, sub) -> { + try { + pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED)); + } catch (TenantNotFoundException e) { + staleSubs.add(id); + log.warn("Cleaning up stale subscription {} for tenant {} due to TenantNotFoundException", id, sub.getTenantId()); + } catch (Exception e) { + log.error("Failed to push subscription {} to manager service", sub, e); + } + }); + if (!staleSubs.isEmpty()) { + subsLock.lock(); + try { + staleSubs.forEach(entityId -> { + subscriptionsByEntityId.remove(entityId); + entityUpdates.remove(entityId); + }); + } finally { + subsLock.unlock(); + } + } } } diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 77ea283827..e8e9f9b9c5 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 8a95b2a16d..cfff3d8dba 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index eda43510b5..8851b99583 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 59640b586d..ddfcaf38e3 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 8b3d7f7f5f..3d3eae4679 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 4cd63dbc1f..91176d088f 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index e60091a16d..9fe969f58d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.media.Schema; @@ -51,10 +52,11 @@ public class TenantProfile extends BaseData implements HasName @NoXss @Schema(description = "Description of the tenant profile", example = "Any text") private String description; - @Schema(description = "Default Tenant profile to be used.", example = "true") + @Schema(description = "Default Tenant profile to be used.", example = "false") + @JsonProperty("default") private boolean isDefault; @Schema(description = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " + - "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true") + "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "false") private boolean isolatedTbRuleEngine; @Schema(description = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.") private transient TenantProfileData profileData; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 40f4b7f42b..51738c75be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.tenant.profile; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfileType; +@Schema @AllArgsConstructor @NoArgsConstructor @Builder @@ -41,34 +43,61 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxOtaPackagesInBytes; private long maxResourceSize; + @Schema(example = "1000:1,20000:60") private String transportTenantMsgRateLimit; + @Schema(example = "1000:1,20000:60") private String transportTenantTelemetryMsgRateLimit; + @Schema(example = "1000:1,20000:60") private String transportTenantTelemetryDataPointsRateLimit; + @Schema(example = "20:1,600:60") private String transportDeviceMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportDeviceTelemetryMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportDeviceTelemetryDataPointsRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayTelemetryMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayTelemetryDataPointsRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayDeviceMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayDeviceTelemetryMsgRateLimit; + @Schema(example = "20:1,600:60") private String transportGatewayDeviceTelemetryDataPointsRateLimit; + @Schema(example = "20:1,600:60") private String tenantEntityExportRateLimit; + @Schema(example = "20:1,600:60") private String tenantEntityImportRateLimit; + @Schema(example = "20:1,600:60") private String tenantNotificationRequestsRateLimit; + @Schema(example = "20:1,600:60") private String tenantNotificationRequestsPerRuleRateLimit; + @Schema(example = "10000000") private long maxTransportMessages; + @Schema(example = "10000000") private long maxTransportDataPoints; + @Schema(example = "4000000") private long maxREExecutions; + @Schema(example = "5000000") private long maxJSExecutions; + @Schema(example = "5000000") private long maxTbelExecutions; + @Schema(example = "0") private long maxDPStorageDays; + @Schema(example = "50") private int maxRuleNodeExecutionsPerMessage; + @Schema(example = "0") private long maxEmails; + @Schema(example = "true") private Boolean smsEnabled; + @Schema(example = "0") private long maxSms; + @Schema(example = "1000") private long maxCreatedAlarms; private String tenantServerRestLimitsConfiguration; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java index d3238b20f8..0662765455 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; @@ -31,6 +33,7 @@ import java.io.Serializable; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultTenantProfileConfiguration.class, name = "DEFAULT")}) +@Schema(discriminatorProperty = "type", discriminatorMapping = {@DiscriminatorMapping(value = "DEFAULT", schema = DefaultTenantProfileConfiguration.class)}) public interface TenantProfileConfiguration extends Serializable { @JsonIgnore diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index cf3134f09b..19ed1e0b40 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 6f650c127b..64b46a9dd2 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index b12140012d..d3cd3d6928 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 06ae7293ba..57652f2645 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index cd77d9da07..54d68afe62 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index b307d328d2..bef66f2c96 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index d196dd13ea..07dec58d66 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index b760129479..b6d6731067 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 23c507cc43..5eb96a1e1a 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 48bd1cfe9c..5dff87a05b 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index e27f7cbc1b..d5946c8545 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 0ec3492c99..d3cd755545 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 952b94ea2f..023b9473dc 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 0c5c7a02c9..368dcec7d8 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 8ad570fceb..96d714f032 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 20d0f6e5d2..d1fead6a4a 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index c4cee40e0d..971357ea40 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index cce6e00501..ec913318a7 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 061c130a69..6aa47ec4bd 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard dao diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java index fc4fff49d0..264ea88326 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.rule; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -51,9 +52,9 @@ public interface RuleNodeRepository extends JpaRepository Pageable pageable); @Query("SELECT r.id FROM RuleNodeEntity r WHERE r.type = :ruleType AND r.configurationVersion < :version") - Page findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType, - @Param("version") int version, - Pageable pageable); + Slice findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType, + @Param("version") int version, + Pageable pageable); List findRuleNodesByRuleChainIdAndExternalIdIn(UUID ruleChainId, List externalIds); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 383c592a31..6700e3614a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntryAggWrapper; +import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.nosql.TbResultSet; import org.thingsboard.server.dao.nosql.TbResultSetFuture; @@ -239,10 +240,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD long minPartition = toPartitionTs(query.getStartTs()); long maxPartition = toPartitionTs(query.getEndTs()); - TbResultSetFuture partitionsFuture = fetchPartitions(tenantId, entityId, query.getKey(), minPartition, maxPartition); - final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>(); - final ListenableFuture> partitionsListFuture = Futures.transformAsync(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); + final ListenableFuture> partitionsListFuture = getPartitionsFuture(tenantId, query, entityId, minPartition, maxPartition); Futures.addCallback(partitionsListFuture, new FutureCallback>() { @Override @@ -416,7 +415,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD readResultsProcessingExecutor); } - private ListenableFuture> getPartitionsFuture(TenantId tenantId, ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) { + private ListenableFuture> getPartitionsFuture(TenantId tenantId, TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) { if (isFixedPartitioning()) { //no need to fetch partitions from DB return Futures.immediateFuture(FIXED_PARTITION); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java index 2da8a7ff45..5a4b20d7a8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.verify; @@ -73,6 +74,9 @@ class DeviceProfileDataValidatorTest { " \"clientOnlyObserveAfterConnect\": 1\n" + " }"; + private static final String msgErrorLwm2mRange = "LwM2M Server ShortServerId must be in range [1 - 65534]!"; + private static final String msgErrorBsRange = "Bootstrap Server ShortServerId must be in range [0 - 65535]!"; + private static final String msgErrorNotNull = " Server ShortServerId must not be null!"; private static final String host = "localhost"; private static final String hostBs = "localhost"; @@ -116,19 +120,50 @@ class DeviceProfileDataValidatorTest { validator.validateDataImpl(tenantId, deviceProfile); verify(validator).validateString("Device profile name", deviceProfile.getName()); } + @Test void testValidateDeviceProfile_Lwm2mBootstrap_ShortServerId_Ok() { Integer shortServerId = 123; Integer shortServerIdBs = 0; - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = - getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(shortServerId, shortServerIdBs)); - DeviceProfile deviceProfile = getDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs); validator.validateDataImpl(tenantId, deviceProfile); verify(validator).validateString("Device profile name", deviceProfile.getName()); } - private DeviceProfile getDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) { + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_null_Error() { + verifyValidationError(123, null, "Bootstrap" + msgErrorNotNull); + } + + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_More_65535_Error() { + verifyValidationError(123, 65536, msgErrorBsRange); + } + + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_Less_0_Error() { + verifyValidationError(123, -1, msgErrorBsRange); + } + + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_null_Error_BootstrapShortServerId_Ok() { + verifyValidationError(null, 1, "LwM2M" + msgErrorNotNull); + } + + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_More_65534_Error_BootstrapShortServerId_Ok() { + verifyValidationError(65535, 111, msgErrorLwm2mRange); + } + + @Test + void testValidateDeviceProfile_Lwm2mShortServerId_Less_1_Error_BootstrapShortServerId_Ok() { + verifyValidationError(0, 111, msgErrorLwm2mRange); + } + + private DeviceProfile getDeviceProfile(Integer shortServerId, Integer shortServerIdBs) { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = + getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(shortServerId, shortServerIdBs)); DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName("default"); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -151,7 +186,7 @@ class DeviceProfileDataValidatorTest { return transportConfiguration; } - private List getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs){ + private List getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs) { List bootstrap = new ArrayList<>(); bootstrap.add(getBootstrapServerCredentialNoSec(false, shortServerId, shortServerIdBs)); bootstrap.add(getBootstrapServerCredentialNoSec(true, shortServerId, shortServerIdBs)); @@ -168,4 +203,11 @@ class DeviceProfileDataValidatorTest { return bootstrapServerCredential; } + private void verifyValidationError(Integer shortServerId, Integer shortServerIdBs, String msgError) { + DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs); + assertThatThrownBy(() -> validator.validateDataImpl(tenantId, deviceProfile)) + .hasMessageContaining(msgError); + + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java index 0fa9383ab1..dcc95b2aff 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java @@ -22,7 +22,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -127,20 +126,15 @@ public class JpaRuleNodeDaoTest extends AbstractJpaDaoTest { @Test public void testFindRuleNodeIdsByTypeAndVersionLessThan() { + // test - search text ignored PageData ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME)); - assertEquals(20, ruleNodeIds.getTotalElements()); - assertEquals(2, ruleNodeIds.getTotalPages()); + assertEquals(0, ruleNodeIds.getTotalElements()); // due to DaoUtil.pageToPageData impl for Slice + assertEquals(0, ruleNodeIds.getTotalPages()); // due to DaoUtil.pageToPageData impl for Slice assertEquals(10, ruleNodeIds.getData().size()); ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0)); - assertEquals(20, ruleNodeIds.getTotalElements()); - assertEquals(2, ruleNodeIds.getTotalPages()); - assertEquals(10, ruleNodeIds.getData().size()); - - // test - search text ignored - ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, StringUtils.randomAlphabetic(5))); - assertEquals(20, ruleNodeIds.getTotalElements()); - assertEquals(2, ruleNodeIds.getTotalPages()); + assertEquals(0, ruleNodeIds.getTotalElements()); // due to DaoUtil.pageToPageData impl for Slice + assertEquals(0, ruleNodeIds.getTotalPages()); // due to DaoUtil.pageToPageData impl for Slice assertEquals(10, ruleNodeIds.getData().size()); } diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 2dd8e54f83..2ed55e141f 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java index 4b95ae1aac..381aac30a0 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java @@ -30,10 +30,12 @@ import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.LwM2mModel; @@ -42,6 +44,10 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; +import org.eclipse.leshan.core.request.BootstrapRequest; +import org.eclipse.leshan.core.request.DeregisterRequest; +import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.monitoring.util.ResourceUtils; @@ -96,7 +102,7 @@ public class Lwm2mClient extends BaseInstanceEnabler implements Destroyable { LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); initializer.setInstancesForObject(SECURITY, security); - initializer.setInstancesForObject(SERVER, new Server(123, TimeUnit.MINUTES.toSeconds(60))); + initializer.setInstancesForObject(SERVER, new Server(123, TimeUnit.MINUTES.toSeconds(5))); initializer.setInstancesForObject(DEVICE, this); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); @@ -139,6 +145,89 @@ public class Lwm2mClient extends BaseInstanceEnabler implements Destroyable { builder.setEncoder(new DefaultLwM2mEncoder(false)); leshanClient = builder.build(); + LwM2mClientObserver observer = new LwM2mClientObserver() { + + @Override + public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {} + + @Override + public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {} + + @Override + public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, + ResponseCode responseCode, String errorMessage, Exception cause) {} + + @Override + public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {} + + @Override + public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { + log.debug("onRegistrationStarted [{}]", request.getEndpointName()); + } + + @Override + public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + log.debug("onRegistrationSuccess [{}] [{}]", request.getEndpointName(), registrationID); + } + + @Override + public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.debug("onRegistrationFailure [{}] [{}] [{}]", request.getEndpointName(), responseCode, errorMessage); + } + + @Override + public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + log.debug("onRegistrationTimeout [{}]", request.getEndpointName()); + } + + @Override + public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { + log.debug("onUpdateStarted [{}]", request.getRegistrationId()); + } + + @Override + public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { + log.debug("onUpdateSuccess [{}]", request.getRegistrationId()); + } + + @Override + public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.debug("onUpdateFailure [{}]", request.getRegistrationId()); + } + + @Override + public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + log.debug("onUpdateTimeout [{}]", request.getRegistrationId()); + } + + @Override + public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + log.debug("onDeregistrationStarted [{}]", request.getRegistrationId()); + } + + @Override + public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + log.debug("onDeregistrationStarted [{}]", request.getRegistrationId()); + } + + @Override + public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.debug("onDeregistrationFailure [{}] [{}] [{}]", request.getRegistrationId(), responseCode, errorMessage); + } + + @Override + public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + log.debug("onDeregistrationTimeout [{}]", request.getRegistrationId()); + } + + @Override + public void onUnexpectedError(Throwable unexpectedError) { + log.debug("onUnexpectedError [{}]", unexpectedError.toString()); + } + + }; + leshanClient.addObserver(observer); + setLeshanClient(leshanClient); leshanClient.start(); diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 4c81e0f31f..ce4de7af17 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index d0864c013a..b7871628f8 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.7.0", + "version": "3.7.1", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 8d3592cbe2..8f67cff304 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index 60ff2e3f4a..9c6ff99615 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index 71ddc91412..9faf269a20 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 708af73ae0..3dab1c01da 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 8b4a6d0747..b799572468 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 5c11fd7adf..fbe4677b8f 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index b7cdae9c95..efedec74ab 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 19ea2a022c..f3c05af81a 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index f06777265e..a6d4e265a4 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 13ee09c3a7..cd6685b7a5 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 35b3809aae..350f49e7fb 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index e70656c49b..8111c1370d 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 081ebd1d3c..e4f4e0afe9 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 6e7cdd4bc1..6bd7266dd8 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.7.0", + "version": "3.7.1", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 103a498a61..e592b1a8e9 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index b0111b9bcd..7f06a09be2 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard netty-mqtt - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index f75e782bef..13125c67f9 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index e4a959a7ec..7e38a4119d 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 8902ef0cee..1448fa650e 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 743baf1ab4..8fe01acdff 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index de48596d70..5ac1421cdb 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index f0cb102778..81de1ded43 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -28,12 +28,14 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; +import java.util.List; + @Slf4j @RuleNode( type = ComponentType.EXTERNAL, name = "rest api call", configClazz = TbRestApiCallNodeConfiguration.class, - version = 1, + version = 2, nodeDescription = "Invoke REST API calls to external REST server", nodeDetails = "Will invoke REST API call GET | POST | PUT | DELETE to external REST server. " + "Message payload added into Request body. Configured attributes can be added into Headers from Message Metadata." + @@ -58,9 +60,6 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { super.init(ctx); TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); httpClient = new TbHttpClient(config, ctx.getSharedEventLoop()); - if (config.isUseRedisQueueForMsgPersistence()) { - log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId()); - } } @Override @@ -88,6 +87,11 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { ((ObjectNode) oldConfiguration).put(PARSE_TO_PLAIN_TEXT, oldConfiguration.get(TRIM_DOUBLE_QUOTES).booleanValue()); ((ObjectNode) oldConfiguration).remove(TRIM_DOUBLE_QUOTES); } + case 1: + if (oldConfiguration.has("useRedisQueueForMsgPersistence")) { + hasChanges = true; + ((ObjectNode) oldConfiguration).remove(List.of("useRedisQueueForMsgPersistence", "trimQueue", "maxQueueSize")); + } break; default: break; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index 7f159bd6a9..7d2ff7167d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -36,7 +36,6 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration upgrade = node.upgrade(0, configJson); - Assertions.assertTrue(upgrade.getFirst()); - Assertions.assertTrue(JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass()).isParseToPlainText()); + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // config for version 2 with upgrade from version 0 + Arguments.of(0, + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\":\"POST\"," + + "\"useSimpleClientHttpFactory\":false,\"ignoreRequestBody\":false,\"enableProxy\":false," + + "\"useSystemProxyProperties\":false,\"proxyScheme\":null,\"proxyHost\":null,\"proxyPort\":0," + + "\"proxyUser\":null,\"proxyPassword\":null,\"readTimeoutMs\":0,\"maxParallelRequestsCount\":0," + + "\"headers\":{\"Content-Type\":\"application/json\"},\"useRedisQueueForMsgPersistence\":false," + + "\"trimQueue\":null,\"maxQueueSize\":null,\"credentials\":{\"type\":\"anonymous\"},\"trimDoubleQuotes\":false}", + true, + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + + "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," + + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + + "\"credentials\": {\"type\": \"anonymous\"}}"), + // config for version 2 with upgrade from version 1 + Arguments.of(1, + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + + "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," + + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + + "\"useRedisQueueForMsgPersistence\": false,\"trimQueue\": null,\"maxQueueSize\": null," + + "\"credentials\": {\"type\": \"anonymous\"}}", + true, + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + + "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," + + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + + "\"credentials\": {\"type\": \"anonymous\"}}") + ); + } + + @Override + protected TbNode getTestNode() { + return restNode; } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java index db632fe3c7..9cab33fcdd 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -19,8 +19,12 @@ import com.google.common.util.concurrent.SettableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; @@ -29,7 +33,10 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; @@ -37,24 +44,30 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.edge.EdgeEventService; +import java.util.Map; import java.util.UUID; +import java.util.stream.Stream; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class TbSendRPCReplyNodeTest { private static final String DUMMY_SERVICE_ID = "testServiceId"; private static final int DUMMY_REQUEST_ID = 0; - private static final UUID DUMMY_SESSION_ID = UUID.randomUUID(); - private static final String DUMMY_DATA = "{\"key\":\"value\"}"; + private static final UUID DUMMY_SESSION_ID = UUID.fromString("4f1d94aa-f6ee-4078-8499-b8e68443f8ad"); + private final String DUMMY_DATA = "{\"key\":\"value\"}"; - TbSendRPCReplyNode node; + private TbSendRPCReplyNode node; + private TbSendRpcReplyNodeConfiguration config; - private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); - private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); + private final TenantId tenantId = TenantId.fromUUID(UUID.fromString("4e2e2336-3376-4238-ba0a-c669b412ca66")); + private final DeviceId deviceId = new DeviceId(UUID.fromString("af64d1b9-8635-47e1-8738-6389df7fe57e")); @Mock private TbContext ctx; @@ -71,14 +84,13 @@ public class TbSendRPCReplyNodeTest { @BeforeEach public void setUp() throws TbNodeException { node = new TbSendRPCReplyNode(); - TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration(); + config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration(); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); } @Test public void sendReplyToTransport() { - Mockito.when(ctx.getRpcService()).thenReturn(rpcService); - + when(ctx.getRpcService()).thenReturn(rpcService); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, getDefaultMetadata(), TbMsgDataType.JSON, DUMMY_DATA, null, null); @@ -91,10 +103,10 @@ public class TbSendRPCReplyNodeTest { @Test public void sendReplyToEdgeQueue() { - Mockito.when(ctx.getTenantId()).thenReturn(tenantId); - Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService); - Mockito.when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create()); - Mockito.when(ctx.getDbCallbackExecutor()).thenReturn(listeningExecutor); + when(ctx.getTenantId()).thenReturn(tenantId); + when(ctx.getEdgeEventService()).thenReturn(edgeEventService); + when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create()); + when(ctx.getDbCallbackExecutor()).thenReturn(listeningExecutor); TbMsgMetaData defaultMetadata = getDefaultMetadata(); defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); @@ -108,6 +120,55 @@ public class TbSendRPCReplyNodeTest { verify(rpcService, never()).sendRpcReplyToDevice(DUMMY_SERVICE_ID, DUMMY_SESSION_ID, DUMMY_REQUEST_ID, DUMMY_DATA); } + @ParameterizedTest + @EnumSource(EntityType.class) + public void testOriginatorEntityTypes(EntityType entityType) { + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, "0f386739-210f-4e23-8739-23f84a172adc"); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + + node.onMsg(ctx, msg); + + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellFailure(eq(msg), throwableCaptor.capture()); + assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class) + .hasMessage(EntityType.DEVICE != entityType ? "Message originator is not a device entity!" + : "Request id is not present in the metadata!"); + } + + @ParameterizedTest + @MethodSource + public void testForAvailabilityOfMetadataAndDataValues(TbMsgMetaData metaData, String errorMsg) { + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, metaData, TbMsg.EMPTY_STRING); + + node.onMsg(ctx, msg); + + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellFailure(eq(msg), throwableCaptor.capture()); + assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class).hasMessage(errorMsg); + } + + @Test + public void verifyDefaultConfig() { + assertThat(config.getServiceIdMetaDataAttribute()).isEqualTo("serviceId"); + assertThat(config.getSessionIdMetaDataAttribute()).isEqualTo("sessionId"); + assertThat(config.getRequestIdMetaDataAttribute()).isEqualTo("requestId"); + } + + private static Stream testForAvailabilityOfMetadataAndDataValues() { + return Stream.of( + Arguments.of(TbMsgMetaData.EMPTY, "Request id is not present in the metadata!"), + Arguments.of(new TbMsgMetaData(Map.of( + "requestId", Integer.toString(DUMMY_REQUEST_ID))), "Service id is not present in the metadata!"), + Arguments.of(new TbMsgMetaData(Map.of( + "requestId", Integer.toString(DUMMY_REQUEST_ID), + "serviceId", DUMMY_SERVICE_ID)), "Session id is not present in the metadata!"), + Arguments.of(new TbMsgMetaData(Map.of( + "requestId", Integer.toString(DUMMY_REQUEST_ID), + "serviceId", DUMMY_SERVICE_ID, "sessionId", + DUMMY_SESSION_ID.toString())), "Request body is empty!") + ); + } + private TbMsgMetaData getDefaultMetadata() { TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration(); TbMsgMetaData metadata = new TbMsgMetaData(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java new file mode 100644 index 0000000000..e9d25e0d5e --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java @@ -0,0 +1,421 @@ +/** + * Copyright © 2016-2024 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.rule.engine.rpc; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; +import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; +import org.thingsboard.rule.engine.api.RuleEngineRpcService; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.data.rpc.RpcError; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Optional; +import java.util.Random; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class TbSendRPCRequestNodeTest { + + private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("d3a47f8b-d863-4c1f-b6f0-2c946b43f21c")); + private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("b052ae59-b9b4-47e8-ac71-39e7124bbd66")); + + private final String MSG_DATA = """ + { + "method": "setGpio", + "params": { + "pin": "23", + "value": 1 + }, + "additionalInfo": "information" + } + """; + + private TbSendRPCRequestNode node; + private TbSendRpcRequestNodeConfiguration config; + + @Mock + private TbContext ctxMock; + @Mock + private RuleEngineRpcService rpcServiceMock; + + @BeforeEach + public void setUp() throws TbNodeException { + node = new TbSendRPCRequestNode(); + config = new TbSendRpcRequestNodeConfiguration().defaultConfiguration(); + var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, configuration); + } + + @Test + public void verifyDefaultConfig() { + assertThat(config.getTimeoutInSeconds()).isEqualTo(60); + } + + @ParameterizedTest + @MethodSource + public void givenOneway_whenOnMsg_thenVerifyRequest(String mdKeyValue, boolean expectedResult) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData msgMetadata = new TbMsgMetaData(); + msgMetadata.putValue("oneway", mdKeyValue); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, msgMetadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + var ruleEngineDeviceRpcRequestCaptor = captureRequest(); + assertThat(ruleEngineDeviceRpcRequestCaptor.getValue().isOneway()).isEqualTo(expectedResult); + } + + private static Stream givenOneway_whenOnMsg_thenVerifyRequest() { + return Stream.of( + Arguments.of("true", true), + Arguments.of("false", false), + Arguments.of(null, false), + Arguments.of("", false) + ); + } + + @Test + public void givenMsgBody_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RuleEngineDeviceRpcRequest.class); + then(rpcServiceMock).should().sendRpcRequestToDevice(requestCaptor.capture(), any(Consumer.class)); + assertThat(requestCaptor.getValue()) + .hasFieldOrPropertyWithValue("method", "setGpio") + .hasFieldOrPropertyWithValue("body", "{\"pin\":\"23\",\"value\":1}") + .hasFieldOrPropertyWithValue("deviceId", DEVICE_ID) + .hasFieldOrPropertyWithValue("tenantId", TENANT_ID) + .hasFieldOrPropertyWithValue("additionalInfo", "information"); + } + + @Test + public void givenRequestIdIsNotSet_whenOnMsg_thenVerifyRequest() { + Random randomMock = mock(Random.class); + given(randomMock.nextInt()).willReturn(123); + ReflectionTestUtils.setField(node, "random", randomMock); + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsg msg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRequestId()).isEqualTo(123); + } + + @Test + public void givenRequestId_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + String data = """ + { + "method": "setGpio", + "params": { + "pin": "23", + "value": 1 + }, + "requestId": 12345 + } + """; + TbMsg msg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, data); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRequestId()).isEqualTo(12345); + } + + @Test + public void givenRequestUUID_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + String requestUUID = "b795a241-5a30-48fb-92d5-46b864d47130"; + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue("requestUUID", requestUUID); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRequestUUID()).isEqualTo(UUID.fromString(requestUUID)); + } + + @ParameterizedTest + @NullAndEmptySource + public void givenInvalidRequestUUID_whenOnMsg_thenVerifyRequest(String requestUUID) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue("requestUUID", requestUUID); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRequestUUID()).isNotNull(); + } + + @Test + public void givenOriginServiceId_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + String originServiceId = "service-id-123"; + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue("originServiceId", originServiceId); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getOriginServiceId()).isEqualTo(originServiceId); + } + + @ParameterizedTest + @NullAndEmptySource + public void givenInvalidOriginServiceId_whenOnMsg_thenVerifyRequest(String originServiceId) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue("originServiceId", originServiceId); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getOriginServiceId()).isNull(); + } + + @Test + public void givenExpirationTime_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + String expirationTime = "2000000000000"; + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(DataConstants.EXPIRATION_TIME, expirationTime); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getExpirationTime()).isEqualTo(Long.parseLong(expirationTime)); + } + + @ParameterizedTest + @NullAndEmptySource + public void givenInvalidExpirationTime_whenOnMsg_thenVerifyRequest(String expirationTime) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(DataConstants.EXPIRATION_TIME, expirationTime); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getExpirationTime()).isGreaterThan(System.currentTimeMillis()); + } + + @Test + public void givenRetries_whenOnMsg_thenVerifyRequest() { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + Integer retries = 3; + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(DataConstants.RETRIES, String.valueOf(retries)); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRetries()).isEqualTo(retries); + } + + @ParameterizedTest + @NullAndEmptySource + public void givenInvalidRetriesValue_whenOnMsg_thenVerifyRequest(String retries) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(DataConstants.RETRIES, retries); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().getRetries()).isNull(); + } + + @ParameterizedTest + @EnumSource(TbMsgType.class) + public void givenTbMsgType_whenOnMsg_thenVerifyRequest(TbMsgType msgType) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsg msg = TbMsg.newMsg(msgType, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + if (msgType == TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE) { + assertThat(requestCaptor.getValue().isRestApiCall()).isTrue(); + return; + } + assertThat(requestCaptor.getValue().isRestApiCall()).isFalse(); + } + + @ParameterizedTest + @MethodSource + public void givenPersistent_whenOnMsg_thenVerifyRequest(String isPersisted, boolean expectedPersistence) { + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(DataConstants.PERSISTENT, isPersisted); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA); + node.onMsg(ctxMock, msg); + + ArgumentCaptor requestCaptor = captureRequest(); + assertThat(requestCaptor.getValue().isPersisted()).isEqualTo(expectedPersistence); + } + + private static Stream givenPersistent_whenOnMsg_thenVerifyRequest() { + return Stream.of( + Arguments.of("true", true), + Arguments.of("false", false), + Arguments.of(null, false), + Arguments.of("", false) + ); + } + + private ArgumentCaptor captureRequest() { + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RuleEngineDeviceRpcRequest.class); + then(rpcServiceMock).should().sendRpcRequestToDevice(requestCaptor.capture(), any(Consumer.class)); + return requestCaptor; + } + + @Test + public void givenRpcResponseWithoutError_whenOnMsg_thenSendsRpcRequest() { + TbMsg outMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + // TODO: replace deprecated method newMsg() + given(ctxMock.newMsg(any(), any(String.class), any(), any(), any(), any())).willReturn(outMsg); + willAnswer(invocation -> { + Consumer consumer = invocation.getArgument(1); + RuleEngineDeviceRpcResponse rpcResponseMock = mock(RuleEngineDeviceRpcResponse.class); + given(rpcResponseMock.getError()).willReturn(Optional.empty()); + given(rpcResponseMock.getResponse()).willReturn(Optional.of(TbMsg.EMPTY_JSON_OBJECT)); + consumer.accept(rpcResponseMock); + return null; + }).given(rpcServiceMock).sendRpcRequestToDevice(any(RuleEngineDeviceRpcRequest.class), any(Consumer.class)); + + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA); + node.onMsg(ctxMock, msg); + + then(ctxMock).should().enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS); + then(ctxMock).should().ack(msg); + } + + @Test + public void givenRpcResponseWithError_whenOnMsg_thenTellFailure() { + TbMsg outMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + + given(ctxMock.getRpcService()).willReturn(rpcServiceMock); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + // TODO: replace deprecated method newMsg() + given(ctxMock.newMsg(any(), any(String.class), any(), any(), any(), any())).willReturn(outMsg); + willAnswer(invocation -> { + Consumer consumer = invocation.getArgument(1); + RuleEngineDeviceRpcResponse rpcResponseMock = mock(RuleEngineDeviceRpcResponse.class); + given(rpcResponseMock.getError()).willReturn(Optional.of(RpcError.NO_ACTIVE_CONNECTION)); + consumer.accept(rpcResponseMock); + return null; + }).given(rpcServiceMock).sendRpcRequestToDevice(any(RuleEngineDeviceRpcRequest.class), any(Consumer.class)); + + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA); + node.onMsg(ctxMock, msg); + + then(ctxMock).should().enqueueForTellFailure(outMsg, RpcError.NO_ACTIVE_CONNECTION.name()); + then(ctxMock).should().ack(msg); + } + + @ParameterizedTest + @EnumSource(EntityType.class) + public void givenOriginatorIsNotDevice_whenOnMsg_thenThrowsException(EntityType entityType) { + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, "ac21a1bb-eabf-4463-8313-24bea1f498d9"); + + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + node.onMsg(ctxMock, msg); + + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + then(ctxMock).should().tellFailure(eq(msg), throwableCaptor.capture()); + assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class) + .hasMessage(EntityType.DEVICE != entityType ? "Message originator is not a device entity!" + : "Method is not present in the message!"); + } + + @ParameterizedTest + @ValueSource(strings = {"method", "params"}) + public void givenMethodOrParamsAreNotPresent_whenOnMsg_thenThrowsException(String key) { + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, "{\"" + key + "\": \"value\"}"); + + node.onMsg(ctxMock, msg); + + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + then(ctxMock).should().tellFailure(eq(msg), throwableCaptor.capture()); + assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class) + .hasMessage(key.equals("method") ? "Params are not present in the message!" : "Method is not present in the message!"); + } +} diff --git a/tools/pom.xml b/tools/pom.xml index 38309f35b0..dfd750aa4f 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index c2c0252d9c..bdd27ae21c 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 7aaf88b59e..b48c3d801d 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index d0b961939a..be11fbdf2f 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index fe2c2be544..a10d3ff9ff 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 49009b83b0..6b1311b949 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 5f236adb9a..a02cd85c58 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index b8a8e4c9a9..f383054ece 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.7.0", + "version": "3.7.1", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index c8b50c4149..931df3a6bc 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.7.0-SNAPSHOT + 3.7.1-SNAPSHOT thingsboard org.thingsboard diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html index 2a3108b8b4..c3fe478ef2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html @@ -28,7 +28,7 @@
-
gateway.device-info.name
+
gateway.device-info.name
@@ -61,7 +61,7 @@
-
gateway.device-info.profile-name
+
gateway.device-info.profile-name
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html new file mode 100644 index 0000000000..afabe53499 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html @@ -0,0 +1,65 @@ + +
+
+
gateway.security
+ + + {{ SecurityTypeTranslationsMap.get(type) | translate }} + + +
+ +
+
gateway.username
+
+ + + + warning + + +
+
+
+
gateway.password
+
+ + + + warning + +
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss new file mode 100644 index 0000000000..f014f56588 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + height: 100%; + display: block; + margin-bottom: 10px; + + .fields-label { + font-weight: 500; + } + + .hide-toggle { + display: none; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts new file mode 100644 index 0000000000..d00813bfd6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts @@ -0,0 +1,132 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + OnDestroy, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { + noLeadTrailSpacesRegex, + RestSecurityType, + RestSecurityTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'tb-rest-connector-security', + templateUrl: './rest-connector-security.component.html', + styleUrls: ['./rest-connector-security.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RestConnectorSecurityComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RestConnectorSecurityComponent), + multi: true + } + ], + standalone: true, + imports: [ + SharedModule, + CommonModule, + ] +}) +export class RestConnectorSecurityComponent implements ControlValueAccessor, Validator, OnDestroy { + BrokerSecurityType = RestSecurityType; + securityTypes: RestSecurityType[] = Object.values(RestSecurityType); + SecurityTypeTranslationsMap = RestSecurityTypeTranslationsMap; + securityFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + private propagateChange = (_: any) => {}; + + constructor(private fb: FormBuilder) { + this.securityFormGroup = this.fb.group({ + type: [RestSecurityType.ANONYMOUS, []], + username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + password: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + }); + this.observeSecurityForm(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfo: any): void { + if (!deviceInfo.type) { + deviceInfo.type = RestSecurityType.ANONYMOUS; + } + this.securityFormGroup.reset(deviceInfo); + this.updateView(deviceInfo); + } + + validate(): ValidationErrors | null { + return this.securityFormGroup.valid ? null : { + securityForm: { valid: false } + }; + } + + private updateView(value: any): void { + this.propagateChange(value); + } + + private updateValidators(type: RestSecurityType): void { + if (type === RestSecurityType.BASIC) { + this.securityFormGroup.get('username').enable({emitEvent: false}); + this.securityFormGroup.get('password').enable({emitEvent: false}); + } else { + this.securityFormGroup.get('username').disable({emitEvent: false}); + this.securityFormGroup.get('password').disable({emitEvent: false}); + } + } + + private observeSecurityForm(): void { + this.securityFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => this.updateView(value)); + + this.securityFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => this.updateValidators(type)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html index ec1fa9a87f..d995136b27 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html @@ -44,7 +44,7 @@
gateway.name
- +
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
gateway.remote-logging-level
@@ -68,7 +84,8 @@
-
+
{{ 'gateway.fill-connector-defaults' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts index 98261a8e57..7cdfbf5808 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -33,6 +33,7 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { Subject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; +import { takeUntil, tap } from "rxjs/operators"; @Component({ selector: 'tb-add-connector-dialog', @@ -40,7 +41,7 @@ import { ResourcesService } from '@core/services/resources.service'; styleUrls: ['./add-connector-dialog.component.scss'], providers: [], }) -export class AddConnectorDialogComponent extends DialogComponent> implements OnDestroy { +export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { connectorForm: UntypedFormGroup; @@ -66,9 +67,15 @@ export class AddConnectorDialogComponent extends DialogComponent { + const useDefaultControl = this.connectorForm.get('useDefaults'); + if (type === ConnectorType.GRPC || type === ConnectorType.CUSTOM) { + useDefaultControl.setValue(false); + } else if (!useDefaultControl.value) { + useDefaultControl.setValue(true); + } + }), + takeUntil(this.destroy$), + ).subscribe() + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss index db98bc07cb..d002a0fc87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss @@ -33,7 +33,6 @@ } .mat-mdc-dialog-content { - max-height: 670px; height: 670px; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html index 8c6224e116..40e557bbbb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html @@ -20,50 +20,52 @@ -
-
-
- - {{ 'gateway.remote-configuration' | translate }} - -
-
- - {{ 'gateway.remote-shell' | translate }} - -
-
- - gateway.thingsboard-host - - info_outlined - - - {{ 'gateway.thingsboard-host-required' | translate }} - - - - gateway.thingsboard-port - - - {{ 'gateway.thingsboard-port-required' | translate }} - - - {{ 'gateway.thingsboard-port-min' | translate }} - - - {{ 'gateway.thingsboard-port-max' | translate }} - - - {{ 'gateway.thingsboard-port-pattern' | translate }} - - info_outlined - - + +
+
+
+ + {{ 'gateway.remote-configuration' | translate }} + +
+
+ + {{ 'gateway.remote-shell' | translate }} + +
+
+ + gateway.thingsboard-host + + info_outlined + + + {{ 'gateway.thingsboard-host-required' | translate }} + + + + gateway.thingsboard-port + + + {{ 'gateway.thingsboard-port-required' | translate }} + + + {{ 'gateway.thingsboard-port-min' | translate }} + + + {{ 'gateway.thingsboard-port-max' | translate }} + + + {{ 'gateway.thingsboard-port-pattern' | translate }} + + info_outlined + + +
@@ -135,7 +137,7 @@
- gateway.password @@ -157,602 +159,613 @@ *ngIf="gatewayConfigGroup.get('thingsboard.security.type').value === 'usernamePassword'" [error]="gatewayConfigGroup.get('thingsboard.security').hasError('atLeastOne') ? ('device.client-id-or-user-name-necessary' | translate) : ''"> - - - + + + +
-
+
-
-
-
- - gateway.logs.date-format - - - {{ 'gateway.logs.date-format-required' | translate }} - - info_outlined - - - - gateway.logs.log-format - - - {{ 'gateway.logs.log-format-required' | translate }} - - info_outlined - - -
-
-
-
gateway.logs.remote
-
- - {{ 'gateway.logs.remote-logs' | translate }} - -
- - gateway.logs.level - - {{ logLevel }} - - -
-
-
gateway.logs.local
- - {{ localLogsConfigTranslateMap.get(logConfig) }} - - -
- - gateway.logs.level - - {{ logLevel }} - + +
+
+
+ + gateway.logs.date-format + + + {{ 'gateway.logs.date-format-required' | translate }} + + info_outlined + - - gateway.logs.file-path - - - {{ 'gateway.logs.file-path-required' | translate }} + + gateway.logs.log-format + + + {{ 'gateway.logs.log-format-required' | translate }} + info_outlined +
-
-
+
+
+
gateway.logs.remote
+
+ + {{ 'gateway.logs.remote-logs' | translate }} + +
+ + gateway.logs.level + + {{ logLevel }} + + +
+
+
gateway.logs.local
+ + {{ localLogsConfigTranslateMap.get(logConfig) }} + + +
+ + gateway.logs.level + + {{ logLevel }} + + + + gateway.logs.file-path + + + {{ 'gateway.logs.file-path-required' | translate }} + + +
+
+
+ + gateway.logs.saving-period + + + {{ 'gateway.logs.saving-period-required' | translate }} + + + {{ 'gateway.logs.saving-period-min' | translate }} + + + + + + {{ period.value | translate }} + + + +
- gateway.logs.saving-period - + gateway.logs.backup-count + - {{ 'gateway.logs.saving-period-required' | translate }} + *ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('required')"> + {{ 'gateway.logs.backup-count-required' | translate }} - {{ 'gateway.logs.saving-period-min' | translate }} + *ngIf="gatewayConfigGroup.get('logs.local.' + logSelector.value + '.backupCount').hasError('min')"> + {{ 'gateway.logs.backup-count-min' | translate }} - - - - - {{ period.value | translate }} - - + info_outlined +
- - gateway.logs.backup-count - - - {{ 'gateway.logs.backup-count-required' | translate }} - - - {{ 'gateway.logs.backup-count-min' | translate }} - - info_outlined - - -
- + +
-
+
-
-
-
gateway.storage
-
gateway.hints.storage
- - - {{ storageTypesTranslationMap.get(storageType) | translate }} - - -
{{ 'gateway.hints.' + gatewayConfigGroup.get('storage.type').value | translate }}
- -
- - gateway.storage-read-record-count - - - {{ 'gateway.storage-read-record-count-required' | translate }} - - - {{ 'gateway.storage-read-record-count-min' | translate }} - - - {{ 'gateway.storage-read-record-count-pattern' | translate }} - - info_outlined - - - - gateway.storage-max-records - - - {{ 'gateway.storage-max-records-required' | translate }} - - - {{ 'gateway.storage-max-records-min' | translate }} - - - {{ 'gateway.storage-max-records-pattern' | translate }} - - info_outlined - - -
-
-
+ +
+
+
gateway.storage
+
gateway.hints.storage
+ + + {{ storageTypesTranslationMap.get(storageType) | translate }} + + +
{{ 'gateway.hints.' + gatewayConfigGroup.get('storage.type').value | translate }}
+ +
- gateway.storage-data-folder-path - - - {{ 'gateway.storage-data-folder-path-required' | translate }} - - info_outlined + gateway.storage-read-record-count + + + {{ 'gateway.storage-read-record-count-required' | translate }} + + + {{ 'gateway.storage-read-record-count-min' | translate }} + + + {{ 'gateway.storage-read-record-count-pattern' | translate }} + + info_outlined - gateway.storage-max-files - - - {{ 'gateway.storage-max-files-required' | translate }} + gateway.storage-max-records + + + {{ 'gateway.storage-max-records-required' | translate }} - - {{ 'gateway.storage-max-files-min' | translate }} + + {{ 'gateway.storage-max-records-min' | translate }} - - {{ 'gateway.storage-max-files-pattern' | translate }} + + {{ 'gateway.storage-max-records-pattern' | translate }} info_outlined + matTooltip="{{ 'gateway.hints.max-records-count' | translate }}">info_outlined -
-
- - gateway.storage-max-read-record-count - - - {{ 'gateway.storage-max-read-record-count-required' | translate }} +
+
+
+ + gateway.storage-data-folder-path + + + {{ 'gateway.storage-data-folder-path-required' | translate }} + + info_outlined + + + + gateway.storage-max-files + + + {{ 'gateway.storage-max-files-required' | translate }} + + + {{ 'gateway.storage-max-files-min' | translate }} + + + {{ 'gateway.storage-max-files-pattern' | translate }} + + info_outlined + + +
+
+ + gateway.storage-max-read-record-count + + + {{ 'gateway.storage-max-read-record-count-required' | translate }} + + + {{ 'gateway.storage-max-read-record-count-min' | translate }} + + + {{ 'gateway.storage-max-read-record-count-pattern' | translate }} + + info_outlined + + + + gateway.storage-max-file-records + + + {{ 'gateway.storage-max-records-required' | translate }} + + + {{ 'gateway.storage-max-records-min' | translate }} + + + {{ 'gateway.storage-max-records-pattern' | translate }} + + info_outlined + + +
+
+
+
+ + gateway.storage-path + + + {{ 'gateway.storage-path-required' | translate }} + + info_outlined + + + + gateway.messages-ttl-check-in-hours + + + {{ 'gateway.messages-ttl-check-in-hours-required' | translate }} + + + {{ 'gateway.messages-ttl-check-in-hours-min' | translate }} + + + {{ 'gateway.messages-ttl-check-in-hours-pattern' | translate }} + + info_outlined + + +
+ + gateway.messages-ttl-in-days + + + {{ 'gateway.messages-ttl-in-days-required' | translate }} - - {{ 'gateway.storage-max-read-record-count-min' | translate }} + + {{ 'gateway.messages-ttl-in-days-min' | translate }} - - {{ 'gateway.storage-max-read-record-count-pattern' | translate }} + + {{ 'gateway.messages-ttl-in-days-pattern' | translate }} info_outlined + matTooltip="{{ 'gateway.hints.ttl-messages-day' | translate }}">info_outlined +
+
+
+
+ +
+ + +
+
+ + {{ 'gateway.grpc' | translate }} + +
+ + {{ 'gateway.permit-without-calls' | translate }} + +
+
+
- gateway.storage-max-file-records - - - {{ 'gateway.storage-max-records-required' | translate }} + gateway.server-port + + info_outlined + + + {{ 'gateway.thingsboard-port-required' | translate }} - - {{ 'gateway.storage-max-records-min' | translate }} + + {{ 'gateway.thingsboard-port-min' | translate }} - - {{ 'gateway.storage-max-records-pattern' | translate }} + + {{ 'gateway.thingsboard-port-max' | translate }} + + + {{ 'gateway.thingsboard-port-pattern' | translate }} + + + gateway.grpc-keep-alive-timeout + info_outlined + matTooltip="{{ 'gateway.hints.grpc-keep-alive-timeout' | translate }}">info_outlined + + {{ 'gateway.grpc-keep-alive-timeout-required' | translate }} + + + {{ 'gateway.grpc-keep-alive-timeout-min' | translate }} + + + {{ 'gateway.grpc-keep-alive-timeout-pattern' | translate }} + -
- -
-
+
+
- gateway.storage-path - - - {{ 'gateway.storage-path-required' | translate }} + gateway.grpc-keep-alive + + info_outlined + + + {{ 'gateway.grpc-keep-alive-required' | translate }} + + + {{ 'gateway.grpc-keep-alive-min' | translate }} + + + {{ 'gateway.grpc-keep-alive-pattern' | translate }} + + + gateway.grpc-min-time-between-pings + info_outlined + matTooltip="{{ 'gateway.hints.grpc-min-time-between-pings' | translate }}">info_outlined + + {{ 'gateway.grpc-min-time-between-pings-required' | translate }} + + + {{ 'gateway.grpc-min-time-between-pings-min' | translate }} + + + {{ 'gateway.grpc-min-time-between-pings-pattern' | translate }} + +
+
- gateway.messages-ttl-check-in-hours - - - {{ 'gateway.messages-ttl-check-in-hours-required' | translate }} + gateway.grpc-max-pings-without-data + + info_outlined + + + {{ 'gateway.grpc-max-pings-without-data-required' | translate }} - - {{ 'gateway.messages-ttl-check-in-hours-min' | translate }} + + {{ 'gateway.grpc-max-pings-without-data-min' | translate }} - - {{ 'gateway.messages-ttl-check-in-hours-pattern' | translate }} + + {{ 'gateway.grpc-max-pings-without-data-pattern' | translate }} + + + gateway.grpc-min-ping-interval-without-data + info_outlined + matTooltip="{{ 'gateway.hints.grpc-min-ping-interval-without-data' | translate }}">info_outlined + + {{ 'gateway.grpc-min-ping-interval-without-data-required' | translate }} + + + {{ 'gateway.grpc-min-ping-interval-without-data-min' | translate }} + + + {{ 'gateway.grpc-min-ping-interval-without-data-pattern' | translate }} + -
- - gateway.messages-ttl-in-days - - - {{ 'gateway.messages-ttl-in-days-required' | translate }} - - - {{ 'gateway.messages-ttl-in-days-min' | translate }} - - - {{ 'gateway.messages-ttl-in-days-pattern' | translate }} - - info_outlined - - + - +
-
+
- -
-
- - {{ 'gateway.grpc' | translate }} - -
- - {{ 'gateway.permit-without-calls' | translate }} + + +
+
+ + {{ 'gateway.statistics.statistics' | translate }} + + gateway.statistics.send-period + + + {{ 'gateway.statistics.send-period-required' | translate }} + + + {{ 'gateway.statistics.send-period-min' | translate }} + + + {{ 'gateway.statistics.send-period-pattern' | translate }} + +
-
-
+
+
gateway.statistics.commands
+
gateway.hints.commands
+ +
+
+
+ + gateway.statistics.attribute-name + + + {{ 'gateway.statistics.attribute-name-required' | translate }} + + info_outlined + + + + gateway.statistics.timeout + + + {{ 'gateway.statistics.timeout-required' | translate }} + + + {{ 'gateway.statistics.timeout-min' | translate }} + + + {{ 'gateway.statistics.timeout-pattern' | translate }} + + info_outlined + + +
+ + gateway.statistics.command + + + {{ 'gateway.statistics.command-required' | translate }} + + + {{ 'gateway.statistics.command-pattern' | translate }} + + info_outlined + + +
+ +
+ +
+
+
+
+
+ + +
+
+
+ + {{ 'gateway.checking-device-activity' | translate }} + +
+
- gateway.server-port - - info_outlined - - - {{ 'gateway.thingsboard-port-required' | translate }} - - - {{ 'gateway.thingsboard-port-min' | translate }} + gateway.inactivity-timeout-seconds + + + {{ 'gateway.inactivity-timeout-seconds-required' | translate }} - - {{ 'gateway.thingsboard-port-max' | translate }} + + {{ 'gateway.inactivity-timeout-seconds-min' | translate }} - - {{ 'gateway.thingsboard-port-pattern' | translate }} + + {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} - - - gateway.grpc-keep-alive-timeout - info_outlined + matTooltip="{{ 'gateway.hints.inactivity-timeout' | translate }}">info_outlined - - {{ 'gateway.grpc-keep-alive-timeout-required' | translate }} - - - {{ 'gateway.grpc-keep-alive-timeout-min' | translate }} - - - {{ 'gateway.grpc-keep-alive-timeout-pattern' | translate }} - -
-
- gateway.grpc-keep-alive - - info_outlined - - - {{ 'gateway.grpc-keep-alive-required' | translate }} + gateway.inactivity-check-period-seconds + + + {{ 'gateway.inactivity-check-period-seconds-required' | translate }} - - {{ 'gateway.grpc-keep-alive-min' | translate }} + + {{ 'gateway.inactivity-check-period-seconds-min' | translate }} - - {{ 'gateway.grpc-keep-alive-pattern' | translate }} + + {{ 'gateway.inactivity-check-period-seconds-pattern' | translate }} - - - gateway.grpc-min-time-between-pings - info_outlined + matTooltip="{{ 'gateway.hints.inactivity-period' | translate }}">info_outlined - - {{ 'gateway.grpc-min-time-between-pings-required' | translate }} - - - {{ 'gateway.grpc-min-time-between-pings-min' | translate }} - - - {{ 'gateway.grpc-min-time-between-pings-pattern' | translate }} -
+
+
+
gateway.advanced
- gateway.grpc-max-pings-without-data - - info_outlined - - - {{ 'gateway.grpc-max-pings-without-data-required' | translate }} - - - {{ 'gateway.grpc-max-pings-without-data-min' | translate }} + gateway.min-pack-send-delay + + + {{ 'gateway.min-pack-send-delay-required' | translate }} - - {{ 'gateway.grpc-max-pings-without-data-pattern' | translate }} + + {{ 'gateway.min-pack-send-delay-min' | translate }} - - - gateway.grpc-min-ping-interval-without-data - info_outlined + matTooltip="{{ 'gateway.hints.minimal-pack-delay' | translate }}">info_outlined - - {{ 'gateway.grpc-min-ping-interval-without-data-required' | translate }} + + + gateway.mqtt-qos + + + {{ 'gateway.mqtt-qos-required' | translate }} - - {{ 'gateway.grpc-min-ping-interval-without-data-min' | translate }} + + {{ 'gateway.mqtt-qos-range' | translate }} - - {{ 'gateway.grpc-min-ping-interval-without-data-pattern' | translate }} + + {{ 'gateway.mqtt-qos-range' | translate }} + info_outlined +
- -
-
-
- -
-
- - {{ 'gateway.statistics.statistics' | translate }} - - - gateway.statistics.send-period - - - {{ 'gateway.statistics.send-period-required' | translate }} - - - {{ 'gateway.statistics.send-period-min' | translate }} - - - {{ 'gateway.statistics.send-period-pattern' | translate }} - - -
-
-
gateway.statistics.commands
-
gateway.hints.commands
- -
-
-
- - gateway.statistics.attribute-name - - - {{ 'gateway.statistics.attribute-name-required' | translate }} - - info_outlined - - - - gateway.statistics.timeout - - - {{ 'gateway.statistics.timeout-required' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - {{ 'gateway.statistics.timeout-pattern' | translate }} - - info_outlined - - -
- - gateway.statistics.command - - - {{ 'gateway.statistics.command-required' | translate }} - - - {{ 'gateway.statistics.command-pattern' | translate }} - - info_outlined - - -
- -
- -
-
-
-
- -
-
-
- - {{ 'gateway.checking-device-activity' | translate }} - -
-
- - gateway.inactivity-timeout-seconds - + + gateway.statistics.check-connectors-configuration + - {{ 'gateway.inactivity-timeout-seconds-required' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')"> + {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - {{ 'gateway.inactivity-timeout-seconds-min' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')"> + {{ 'gateway.statistics.check-connectors-configuration-min' | translate }} - {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')"> + {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} - info_outlined - - - gateway.inactivity-check-period-seconds - - - {{ 'gateway.inactivity-check-period-seconds-required' | translate }} - - - {{ 'gateway.inactivity-check-period-seconds-min' | translate }} - - - {{ 'gateway.inactivity-check-period-seconds-pattern' | translate }} - - info_outlined - - -
-
-
-
gateway.advanced
-
- - gateway.min-pack-send-delay - - - {{ 'gateway.min-pack-send-delay-required' | translate }} - - - {{ 'gateway.min-pack-send-delay-min' | translate }} - - info_outlined - - - - gateway.mqtt-qos - - - {{ 'gateway.mqtt-qos-required' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - info_outlined - - -
- - gateway.statistics.check-connectors-configuration - - - {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-min' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} - - +
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss index f7562c748e..9d72d1d759 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss @@ -55,7 +55,7 @@ } .statistics-container { - width: 50%; + width: 100%; .command-container { width: 100%; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 3a7f5403b9..eb7afe0cc5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -46,7 +46,7 @@ + (click)="$event.stopPropagation(); onEnableConnector(attribute)"> @@ -178,18 +178,36 @@
gateway.name
- + warning
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
gateway.logs-configuration
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 4f1b6b72a9..66d33b9a06 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -62,7 +62,7 @@ import { } from './gateway-widget.models'; import { MatDialog } from '@angular/material/dialog'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; -import { takeUntil } from 'rxjs/operators'; +import { debounceTime, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; @@ -148,6 +148,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private destroy$ = new Subject(); private subscription: IWidgetSubscription; + private attributeUpdateSubject = new Subject(); constructor(protected store: Store, private fb: FormBuilder, @@ -180,6 +181,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie basicConfig: this.fb.group({}) }); this.connectorForm.disable(); + + this.observeAttributeChange(); } get portErrorTooltip(): string { @@ -238,12 +241,23 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.dataSource.sort = this.sort; this.dataSource.sortingDataAccessor = (data: AttributeData, sortHeaderId: string) => { - if (sortHeaderId === 'syncStatus') { - return this.isConnectorSynced(data) ? 1 : 0; - } else if (sortHeaderId === 'enabled') { - return this.activeConnectors.includes(data.key) ? 1 : 0; + switch (sortHeaderId) { + case 'syncStatus': + return this.isConnectorSynced(data) ? 1 : 0; + + case 'enabled': + return this.activeConnectors.includes(data.key) ? 1 : 0; + + case 'errors': + const errors = this.getErrorsCount(data); + if (typeof errors === 'string') { + return this.sort.direction.toUpperCase() === Direction.DESC ? -1 : Infinity; + } + return errors; + + default: + return data[sortHeaderId] || data.value[sortHeaderId]; } - return data[sortHeaderId] || data.value[sortHeaderId]; }; if (this.device) { @@ -524,31 +538,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } - enableConnector(attribute: AttributeData): void { - const wasEnabled = this.activeConnectors.includes(attribute.key); - const scopeOld = wasEnabled ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; - const scopeNew = !wasEnabled ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; + onEnableConnector(attribute: AttributeData): void { attribute.value.ts = new Date().getTime(); - const tasks = [this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, [{ - key: 'active_connectors', - value: this.activeConnectors - }]), this.attributeService.saveEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, [{ - key: 'inactive_connectors', - value: this.inactiveConnectors - }]), this.attributeService.deleteEntityAttributes(this.device, scopeOld, [attribute]), - this.attributeService.saveEntityAttributes(this.device, scopeNew, [attribute])]; - if (wasEnabled) { - const index = this.activeConnectors.indexOf(attribute.key); - this.activeConnectors.splice(index, 1); - this.inactiveConnectors.push(attribute.key); - } else { - const index = this.inactiveConnectors.indexOf(attribute.key); - this.inactiveConnectors.splice(index, 1); - this.activeConnectors.push(attribute.key); - } - forkJoin(tasks).subscribe(_ => { - this.updateData(true); - }); + + this.updateActiveConnectorKeys(attribute.key); + + this.attributeUpdateSubject.next(attribute); } getErrorsCount(attribute: AttributeData): string { @@ -599,6 +594,55 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.get(formControlName)?.patchValue('tb_gw_' + generateSecret(5)); } + private observeAttributeChange(): void { + this.attributeUpdateSubject.pipe( + debounceTime(300), + tap((attribute: AttributeData) => this.executeAttributeUpdates(attribute)), + takeUntil(this.destroy$), + ).subscribe(); + } + + private updateActiveConnectorKeys(key: string): void { + const wasEnabled = this.activeConnectors.includes(key); + if (wasEnabled) { + const index = this.activeConnectors.indexOf(key); + if (index !== -1) this.activeConnectors.splice(index, 1); + this.inactiveConnectors.push(key); + } else { + const index = this.inactiveConnectors.indexOf(key); + if (index !== -1) this.inactiveConnectors.splice(index, 1); + this.activeConnectors.push(key); + } + } + + private executeAttributeUpdates(attribute: AttributeData): void { + forkJoin(this.getAttributeExecutionTasks(attribute)) + .pipe( + take(1), + tap(() => this.updateData(true)), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + private getAttributeExecutionTasks(attribute: AttributeData): Observable[] { + const isActive = this.activeConnectors.includes(attribute.key); + const scopeOld = isActive ? AttributeScope.SERVER_SCOPE : AttributeScope.SHARED_SCOPE; + const scopeNew = isActive ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; + + return [ + this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, [{ + key: 'active_connectors', + value: this.activeConnectors + }]), + this.attributeService.saveEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, [{ + key: 'inactive_connectors', + value: this.inactiveConnectors + }]), + this.attributeService.deleteEntityAttributes(this.device, scopeOld, [attribute]), + this.attributeService.saveEntityAttributes(this.device, scopeNew, [attribute])]; + } + private onDataUpdateError(e: any): void { const exceptionData = this.utils.parseException(e); let errorText = exceptionData.name; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html index d36a4c0a51..098a8ce5e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html @@ -21,7 +21,7 @@ [active]="activeLink.name === link.name"> {{ link.name }} - @@ -31,7 +31,7 @@ - {{ 'widgets.gateway.status' | translate }} + {{ 'widgets.gateway.level' | translate }} {{ attribute.status }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts index 0607268edc..d029279c2b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts @@ -164,6 +164,10 @@ export class GatewayLogsComponent implements AfterViewInit { } } + trackByLogTs(_: number, log: GatewayLogData): number { + return log.ts; + } + private changeSubscription() { if (this.ctx.datasources && this.ctx.datasources[0].entity && this.ctx.defaultSubscription.options.datasources) { this.ctx.defaultSubscription.options.datasources[0].dataKeys = [{ @@ -178,5 +182,4 @@ export class GatewayLogsComponent implements AfterViewInit { }; } } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html index c0f6997a36..2fdfcfbf0a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -18,8 +18,8 @@
{{ 'gateway.rpc.templates-title' | translate }}
- - {{template.name}} + + {{template.name}} -
- {{ 'gateway.rpc.security' | translate }} -
-
- {{ 'gateway.rpc.security-name' | translate }} - {{ 'gateway.rpc.value' | translate }} - -
- -
- - - - - - - - delete - - -
-
- -
+ @@ -478,9 +447,6 @@ - - - {{ 'gateway.rpc.method' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 9838fd2df2..c515cbc201 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, FormArray, @@ -53,6 +53,8 @@ import { } from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; +import { takeUntil, tap } from "rxjs/operators"; +import { Subject } from "rxjs"; @Component({ selector: 'tb-gateway-service-rpc-connector', @@ -66,7 +68,7 @@ import { deepClone } from '@core/utils'; } ] }) -export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor { +export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, ControlValueAccessor { @Input() connectorType: ConnectorType; @@ -105,6 +107,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue private propagateChange = (v: any) => { } + private destroy$ = new Subject(); constructor(private fb: FormBuilder, private dialog: MatDialog,) { @@ -114,17 +117,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); this.commandForm.valueChanges.subscribe(value => { const httpHeaders = {}; - const security = {}; switch (this.connectorType) { case ConnectorType.REST: value.httpHeaders.forEach(data => { httpHeaders[data.headerName] = data.value; }) value.httpHeaders = httpHeaders; - value.security.forEach(data => { - security[data.securityName] = data.value; - }) - value.security = security; break; case ConnectorType.REQUEST: value.httpHeaders.forEach(data => { @@ -138,6 +136,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue } }); this.isMQTTWithResponse = this.fb.control(false); + this.observeMQTTWithResponse(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } connectorParamsFormGroupByType(type: ConnectorType): FormGroup { @@ -148,7 +152,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue formGroup = this.fb.group({ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], + responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], responseTimeout: [null, [Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], }) @@ -252,7 +256,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), - security: this.fb.array([]) + security: [{}, [Validators.required]] }) break; case ConnectorType.REQUEST: @@ -269,7 +273,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue }) break; case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: formGroup = this.fb.group({ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], arguments: this.fb.array([]), @@ -312,22 +315,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue oidsFA.removeAt(index); } - addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) { - const securityFA = this.commandForm.get('security') as FormArray; - const formGroup = this.fb.group({ - securityName: [value.securityName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }) - if (securityFA) { - securityFA.push(formGroup, {emitEvent: false}); - } - } - - removeHTTPSecurity(index: number) { - const oidsFA = this.commandForm.get('security') as FormArray; - oidsFA.removeAt(index); - } - getFormArrayControls(path: string) { return (this.commandForm.get(path) as FormArray).controls as FormControl[]; } @@ -402,18 +389,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue break; case ConnectorType.REST: this.clearFromArrayByName("httpHeaders"); - this.clearFromArrayByName("security"); - value.security && Object.entries(value.security).forEach(securityHeader => { - this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string}) - }) - delete value.security; value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) }) delete value.httpHeaders; break; case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: this.clearFromArrayByName("arguments"); value.arguments.forEach(value => { this.addOCPUAArguments(value) @@ -424,4 +405,14 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue this.commandForm.patchValue(value, {onlySelf: false}); } } + + private observeMQTTWithResponse(): void { + this.isMQTTWithResponse.valueChanges.pipe( + tap((isActive: boolean) => { + const responseControl = this.commandForm.get('responseTopicExpression'); + isActive ? responseControl.enable() : responseControl.disable(); + }), + takeUntil(this.destroy$), + ).subscribe(); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index b5cde980f9..dadd1a38d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -150,7 +150,6 @@ export class GatewayServiceRPCComponent implements OnInit { case ConnectorType.BACNET: case ConnectorType.CAN: case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: return params.method; case ConnectorType.BLE: case ConnectorType.OCPP: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 0e143cff36..095d14ae76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -125,7 +125,6 @@ export enum ConnectorType { MODBUS = 'modbus', GRPC = 'grpc', OPCUA = 'opcua', - OPCUA_ASYNCIO = 'opcua_asyncio', BLE = 'ble', REQUEST = 'request', CAN = 'can', @@ -145,7 +144,6 @@ export const GatewayConnectorDefaultTypesTranslatesMap = new Map([ [SNMPMethods.TABLE, 'gateway.rpc.table'], [SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'], [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], - [SNMPMethods.BULKGET, 'gateway.rpc.bul-kget'], + [SNMPMethods.BULKGET, 'gateway.rpc.bulk-get'], [SNMPMethods.WALKS, 'gateway.rpc.walk'] ]) @@ -360,6 +358,18 @@ export const BrokerSecurityTypeTranslationsMap = new Map( + [ + [RestSecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], + [RestSecurityType.BASIC, 'gateway.broker.security-types.basic'], + ] +); + export const MqttVersions = [ { name: 3.1, value: 3 }, { name: 3.11, value: 4 }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts index 0ae1bdc4f5..37879144cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts @@ -517,7 +517,7 @@ class PersistentDatasource implements DataSource { if (!this.executingRpcRequest || rejection.status === 504) { this.subscription.rpcRejection = rejection; if (rejection.status === 504) { - this.subscription.rpcErrorText = 'Request Timeout.'; + this.subscription.rpcErrorText = 'Request timeout'; } else { this.subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; const error = parseHttpErrorMessage(rejection, this.translate); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index d345f5954f..6d2cfb6fe3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -100,6 +100,7 @@ import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-c import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component'; import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component'; import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobile-app-qrcode-widget.component'; +import { RestConnectorSecurityComponent } from '@home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component'; @NgModule({ declarations: @@ -168,13 +169,14 @@ import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobi PolarAreaWidgetComponent, RadarChartWidgetComponent ], - imports: [ - CommonModule, - SharedModule, - RpcWidgetsModule, - HomePageWidgetsModule, - SharedHomeComponentsModule - ], + imports: [ + CommonModule, + SharedModule, + RpcWidgetsModule, + HomePageWidgetsModule, + SharedHomeComponentsModule, + RestConnectorSecurityComponent + ], exports: [ EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 5106b622dd..0f4e8a3cbe 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2805,8 +2805,8 @@ "copy-username": "Copy username", "copy-password": "Copy password", "copy-client-id": "Copy client ID", - "connector-created": "Connector created.", - "connector-updated": "Connector updated.", + "connector-created": "Connector created", + "connector-updated": "Connector updated", "rpc-command-save-template": "Save Template", "rpc-command-send": "Send", "rpc-command-result": "Response", @@ -3237,18 +3237,18 @@ "password": "MQTT password for the gateway form platform server", "ca-cert": "Path to CA certificate file", "date-form": "Date format in log message", - "data-folder": "Path to folder, that will contains data (Relative or Absolute)", + "data-folder": "Path to the folder that will contain data (Relative or Absolute)", "log-format": "Log message format", "remote-log": "Enables remote logging and logs reading from the gateway", "backup-count": "If backup count is > 0, when a rollover is done, no more than backup count files are kept - the oldest ones are deleted", "storage": "Provides configuration for saving incoming data before it is sent to the platform", - "max-file-count": "Maximum count of file that will be created", - "max-read-count": "Count of messages to get from storage and send to platform", + "max-file-count": "Maximum number of files that will be created", + "max-read-count": "Number of messages to retrieve from the storage and send to platform", "max-records": "Maximum count of records that will be stored in one file", - "read-record-count": "Count of messages to get from storage and send to platform", - "max-records-count": "Maximum count of data in storage before send to platform", - "ttl-check-hour": "How often will Gateway check data for obsolescence", - "ttl-messages-day": "Maximum days that storage will save data", + "read-record-count": "Number of messages to retrieve from the storage and send to platform", + "max-records-count": "Maximum number of data entries in storage before sending to platform", + "ttl-check-hour": "How often will the Gateway check data for obsolescence", + "ttl-messages-day": "Maximum number of days that the storage will retain data", "commands": "Commands for collecting additional statistic", "attribute": "Statistic telemetry key", "timeout": "Timeout for command executing", @@ -4088,7 +4088,7 @@ "target-device-is-not-set": "Target device is not set!", "invalid-target-entity": "RPC commands are not supported by {{entityType}} entity.", "failed-to-resolve-target-device": "Failed to resolve target device!", - "request-timeout": "Request Timeout.", + "request-timeout": "Request timeout", "rpc-http-error": "Error: {{status}} - {{statusText}}" } }, @@ -6080,8 +6080,8 @@ "event-key-contains": "Event key contains...", "show-connector": "Show for the connector", "connector-state-param-key": "Connector state parameter key", - "status": "Status", "message": "Message", + "level": "Level", "created-time": "Created time" }, "gauge": { diff --git a/ui-ngx/src/assets/locale/locale.constant-lt_LT.json b/ui-ngx/src/assets/locale/locale.constant-lt_LT.json index 21338bf1f0..8b85f7f480 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lt_LT.json +++ b/ui-ngx/src/assets/locale/locale.constant-lt_LT.json @@ -6785,7 +6785,6 @@ "event-key-contains": "Event key contains...", "show-connector": "Show for the connector", "connector-state-param-key": "Connector state parameter key", - "status": "Status", "message": "Message", "created-time": "Created time" }, diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json index 82cd3639b4..400d42c7d0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json +++ b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json @@ -6808,7 +6808,6 @@ "event-key-contains": "Klucz zdarzenia zawiera...", "show-connector": "Show for the connector", "connector-state-param-key": "Connector state parameter key", - "status": "Status", "message": "Message", "created-time": "Created time" },