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 8f426b1624..ec6388abc3 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/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/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..6e13196b0e 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 @@ -58,6 +58,22 @@
+
+
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 8aeaeef2ed..d622afa789 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,737 +20,749 @@ -
-
-
- - {{ '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 + + +
-
-
-
security.security
- - - {{ securityType.value | translate }} - - - security.access-token - - - {{ 'security.access-token-required' | translate }} - - - - info_outlined - - -
- - security.clientId - - - {{ 'security.clientId-required' | translate }} +
+
security.security
+ + + {{ securityType.value | translate }} + + + security.access-token + + + {{ 'security.access-token-required' | translate }} info_outlined + matTooltip="{{ 'gateway.hints.token' | translate }}">info_outlined - - security.username - - - {{ 'security.username-required' | translate }} - +
+ + security.clientId + + + {{ 'security.clientId-required' | translate }} + + + + info_outlined + + + + security.username + + + {{ 'security.username-required' | translate }} + + + + info_outlined + + +
+ + gateway.password + info_outlined + matTooltip="{{ 'gateway.hints.password' | translate }}">info_outlined -
- - gateway.password - - - - info_outlined - - - - - - + + + +
-
+ -
-
-
- - 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.saving-period - + 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.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.inactivity-timeout-seconds-required' | translate }} - - - {{ 'gateway.inactivity-timeout-seconds-min' | translate }} - - - {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} - - info_outlined - - - - gateway.inactivity-check-period-seconds - + + gateway.statistics.check-connectors-configuration + - {{ 'gateway.inactivity-check-period-seconds-required' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')"> + {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - {{ 'gateway.inactivity-check-period-seconds-min' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')"> + {{ 'gateway.statistics.check-connectors-configuration-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 }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')"> + {{ 'gateway.statistics.check-connectors-configuration-pattern' | 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..19353aa139 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)"> @@ -190,6 +190,22 @@
+
+
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/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 695c2454ae..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", @@ -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" },