Browse Source

Merge branch 'improvement/gateway-dashboard' into fix/3911-connector-name-logs

pull/11070/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
8e7e1b7f9b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      application/src/main/data/json/edge/instructions/install/centos/instructions.md
  2. 14
      application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
  3. 197
      application/src/main/data/json/tenant/dashboards/gateways.json
  4. 65
      application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java
  5. 71
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java
  6. 55
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java
  7. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html
  8. 65
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html
  9. 29
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss
  10. 132
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts
  11. 19
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html
  12. 25
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts
  13. 1
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss
  14. 1250
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html
  15. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss
  16. 18
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  17. 104
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  18. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html
  19. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts
  20. 9
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html
  21. 15
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss
  22. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts
  23. 36
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html
  24. 55
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts
  25. 1
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts
  26. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  27. 16
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  28. 20
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  29. 1
      ui-ngx/src/assets/locale/locale.constant-lt_LT.json
  30. 1
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json

14
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}
```

14
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}
```

197
application/src/main/data/json/tenant/dashboards/gateways.json

@ -248,7 +248,7 @@
"type": "customPretty",
"customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\r\n (ngSubmit)=\"save($event)\" class=\"add-entity-form\">\r\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\r\n <h2>Add gateway</h2>\r\n <span fxFlex></span>\r\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\r\n <mat-icon class=\"material-icons\">close</mat-icon>\r\n </button>\r\n </mat-toolbar>\r\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\r\n </mat-progress-bar>\r\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\r\n <div mat-dialog-content fxLayout=\"column\">\r\n <div fxLayout=\"row\" fxLayoutGap=\"8px\" fxLayout.xs=\"column\" fxLayoutGap.xs=\"0\">\r\n <mat-form-field fxFlex class=\"mat-block\">\r\n <mat-label>Name</mat-label>\r\n <input matInput formControlName=\"entityName\" required>\r\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\r\n Gateway name is required.\r\n </mat-error>\r\n </mat-form-field>\r\n </div>\r\n <div fxLayout=\"row\" fxLayoutGap=\"8px\" fxLayout.xs=\"column\" fxLayoutGap.xs=\"0\">\r\n <tb-entity-subtype-autocomplete\r\n fxFlex\r\n class=\"mat-block\"\r\n formControlName=\"type\"\r\n [required]=\"true\"\r\n [entityType]=\"'DEVICE'\"\r\n ></tb-entity-subtype-autocomplete>\r\n </div>\r\n </div>\r\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\r\n <button mat-button color=\"primary\"\r\n type=\"button\"\r\n [disabled]=\"(isLoading$ | async)\"\r\n (click)=\"cancel()\" cdkFocusInitial>\r\n Cancel\r\n </button>\r\n <button mat-button mat-raised-button color=\"primary\"\r\n type=\"submit\"\r\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\r\n Create\r\n </button>\r\n </div>\r\n</form>\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": "<div style=\"width: 100%; height: 100%; padding: 0;\" fxFlex fxLayout=\"column\">\r\n <mat-tab-group [(selectedIndex)]=\"selectedTabIndex\">\r\n <mat-tab label=\"All\" value=\"gateway_devices_0\"></mat-tab>\r\n <mat-tab label=\"MQTT\" value=\"gateway_devices_1\"></mat-tab>\r\n <mat-tab label=\"MODBUS\" value=\"gateway_devices_2\"></mat-tab>\r\n <mat-tab label=\"GRPC\" value=\"gateway_devices_3\"></mat-tab>\r\n <mat-tab label=\"OPCUA\" value=\"gateway_devices_4\"> </mat-tab>\r\n <mat-tab label=\"OPCUA ASYNCIO\" value=\"gateway_devices_5\"></mat-tab>\r\n <mat-tab label=\"BLE\" value=\"gateway_devices_6\"></mat-tab>\r\n <mat-tab label=\"REQUEST\" value=\"gateway_devices_7\"></mat-tab>\r\n <mat-tab label=\"CAN\" value=\"gateway_devices_8\"></mat-tab>\r\n <mat-tab label=\"BACNET\" value=\"gateway_devices_9\"></mat-tab>\r\n <mat-tab label=\"ODBC\" value=\"gateway_devices_10\"></mat-tab>\r\n <mat-tab label=\"REST\" value=\"gateway_devices_11\"></mat-tab>\r\n <mat-tab label=\"SNMP\" value=\"gateway_devices_12\"></mat-tab>\r\n <mat-tab label=\"FTP\" value=\"gateway_devices_13\"></mat-tab>\r\n <mat-tab label=\"SOCKET\" value=\"gateway_devices_14\"></mat-tab>\r\n <mat-tab label=\"XMPP\" value=\"gateway_devices_15\"></mat-tab>\r\n <mat-tab label=\"OCCP\" value=\"gateway_devices_16\"></mat-tab>\r\n <mat-tab label=\"CUSTOM\" value=\"gateway_devices_17\"></mat-tab>\r\n </mat-tab-group><tb-dashboard-state *ngIf=\"selectedTabIndex == 1\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_1\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 2\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_2\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 3\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_3\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 4\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_4\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 5\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_5\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 6\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_6\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 7\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_7\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 8\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_8\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 9\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_9\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 10\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_10\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 11\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_11\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 12\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_12\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 13\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_13\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 14\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_14\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 15\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_15\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 16\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_16\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 17\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_17\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"!selectedTabIndex\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_0\"></tb-dashboard-state>\r\n</div>\r\n",
"markdownTextPattern": "<div style=\"width: 100%; height: 100%; padding: 0;\" fxFlex fxLayout=\"column\">\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 6\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_6\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 7\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_7\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 8\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_8\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 9\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_9\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 10\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_10\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 11\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_11\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 12\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_12\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 13\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_13\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 14\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_14\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 15\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_15\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 16\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_16\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 17\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_17\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"!selectedTabIndex\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_0\"></tb-dashboard-state>\r\n</div>\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 `<span class='${cssClass}'>${value}</span>`;",
"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,

65
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();
}

71
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;
}
}

55
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<String, EdgeUpgradeInfo> 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;
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html

@ -28,7 +28,7 @@
<div class="tb-form-table-body no-gap">
<div class="tb-form-table-row tb-form-row no-border same-padding top-same-padding"
[class.bottom-same-padding]="deviceInfoType !== DeviceInfoType.FULL">
<div class="fixed-title-width" translate>gateway.device-info.name</div>
<div class="fixed-title-width tb-required" translate>gateway.device-info.name</div>
<div class="tb-flex no-gap raw-value-option" *ngIf="useSource">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="deviceNameExpressionSource">
@ -61,7 +61,7 @@
</div>
<div class="tb-form-table-row tb-form-row no-border same-padding bottom-same-padding"
*ngIf="deviceInfoType === DeviceInfoType.FULL">
<div class="fixed-title-width" translate>gateway.device-info.profile-name</div>
<div class="fixed-title-width tb-required" translate>gateway.device-info.profile-name</div>
<div class="tb-flex no-gap raw-value-option" *ngIf="useSource">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="deviceProfileExpressionSource">

65
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html

@ -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.
-->
<div class="tb-form-row space-between same-padding tb-flex column" [formGroup]="securityFormGroup">
<div class="tb-flex row space-between align-center no-gap fill-width">
<div class="fields-label" translate>gateway.security</div>
<tb-toggle-select formControlName="type" appearance="fill">
<tb-toggle-option *ngFor="let type of securityTypes" [value]="type">
{{ SecurityTypeTranslationsMap.get(type) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="securityFormGroup.get('type').value === BrokerSecurityType.BASIC">
<div class="tb-form-row space-between tb-flex fill-width">
<div class="fixed-title-width" translate>gateway.username</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="username" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.username-required') | translate"
*ngIf="securityFormGroup.get('username').hasError('required') && securityFormGroup.get('username').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
<div class="tb-form-row space-between tb-flex fill-width">
<div class="fixed-title-width" translate>gateway.password</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="password" name="value" formControlName="password" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.password-required') | translate"
*ngIf="securityFormGroup.get('password').hasError('required')
&& securityFormGroup.get('password').touched"
class="tb-error">
warning
</mat-icon>
<div [class.hide-toggle]="securityFormGroup.get('password').hasError('required')" class="tb-flex no-gap align-center fill-height" matSuffix>
<tb-toggle-password class="tb-flex align-center fill-height"></tb-toggle-password>
</div>
</mat-form-field>
</div>
</div>
</ng-container>
</div>

29
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;
}
}

132
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<void>();
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));
}
}

19
ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html

@ -58,6 +58,22 @@
</mat-form-field>
</div>
</div>
<div *ngIf="connectorForm.get('type').value === connectorType.CUSTOM" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.connectors-table-class</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="class" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div *ngIf="connectorForm.get('type').value === connectorType.GRPC" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.connectors-table-key</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="key" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.remote-logging-level</div>
<div class="tb-flex no-gap">
@ -68,7 +84,8 @@
</mat-form-field>
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div *ngIf="connectorForm.get('type').value !== connectorType.GRPC && connectorForm.get('type').value !== connectorType.CUSTOM"
class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="useDefaults">
<mat-label tb-hint-tooltip-icon="{{ 'gateway.fill-connector-defaults-hint' | translate }}">
{{ 'gateway.fill-connector-defaults' | translate }}

25
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<AddConnectorDialogComponent, BaseData<HasId>> implements OnDestroy {
export class AddConnectorDialogComponent extends DialogComponent<AddConnectorDialogComponent, BaseData<HasId>> implements OnInit, OnDestroy {
connectorForm: UntypedFormGroup;
@ -66,9 +67,15 @@ export class AddConnectorDialogComponent extends DialogComponent<AddConnectorDia
logLevel: [GatewayLogLevel.INFO, []],
useDefaults: [true, []],
sendDataOnlyOnChange: [false, []],
class: ['', []],
key: ['auto', []],
});
}
ngOnInit(): void {
this.observeTypeChange();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
@ -118,4 +125,18 @@ export class AddConnectorDialogComponent extends DialogComponent<AddConnectorDia
return null;
};
}
private observeTypeChange(): void {
this.connectorForm.get('type').valueChanges.pipe(
tap((type: ConnectorType) => {
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()
}
}

1
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;
}

1250
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html

File diff suppressed because it is too large

2
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%;

18
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html

@ -46,7 +46,7 @@
</mat-header-cell>
<mat-cell *matCellDef="let attribute">
<mat-slide-toggle [checked]="activeConnectors.includes(attribute.key)"
(click)="$event.stopPropagation(); enableConnector(attribute)"></mat-slide-toggle>
(click)="$event.stopPropagation(); onEnableConnector(attribute)"></mat-slide-toggle>
</mat-cell>
</ng-container>
<ng-container matColumnDef="key">
@ -190,6 +190,22 @@
</mat-form-field>
</div>
</div>
<div *ngIf="connectorForm.get('type').value === connectorType.CUSTOM" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.connectors-table-class</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="class" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div *ngIf="connectorForm.get('type').value === connectorType.GRPC" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.connectors-table-key</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="key" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>gateway.logs-configuration</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">

104
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<void>();
private subscription: IWidgetSubscription;
private attributeUpdateSubject = new Subject<AttributeData>();
constructor(protected store: Store<AppState>,
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<any>[] {
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;

4
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 }} </a>
</nav>
<mat-tab-nav-panel #tabPanel></mat-tab-nav-panel>
<table mat-table [dataSource]="dataSource"
<table mat-table [dataSource]="dataSource" [trackBy]="trackByLogTs"
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()"
matSortDisableClear>
<ng-container matColumnDef="ts">
@ -31,7 +31,7 @@
</mat-cell>
</ng-container>
<ng-container matColumnDef="status">
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 10%">{{ 'widgets.gateway.status' | translate }}</mat-header-cell>
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 10%">{{ 'widgets.gateway.level' | translate }}</mat-header-cell>
<mat-cell *matCellDef="let attribute">
<span [class]="statusClass(attribute.status)">{{ attribute.status }}</span>
</mat-cell>

5
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 {
};
}
}
}

9
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html

@ -18,8 +18,8 @@
<div class="mat-subtitle-1 title">{{ 'gateway.rpc.templates-title' | translate }}</div>
<mat-expansion-panel hideToggle *ngFor="let template of rpcTemplates">
<mat-expansion-panel-header>
<mat-panel-title>
{{template.name}}
<mat-panel-title class="template-name">
<span matTooltip="{{template.name}}" matTooltipPosition="above">{{template.name}}</span>
</mat-panel-title>
<mat-panel-description>
<button mat-icon-button matTooltip="Delete" (click)="deleteTemplate($event, template)">
@ -47,7 +47,10 @@
<div *ngIf="!isObject(config.value) else RPCObjectRow"
[ngClass]="{'boolean-true': config.value === true,
'boolean-false': config.value === false }">
{{config.value}}</div>
<ng-container *ngIf="config.key === 'method' else value" [ngTemplateOutlet]="SNMPMethod"></ng-container>
</div>
<ng-template #value>{{ config.value }}</ng-template>
<ng-template #SNMPMethod>{{ SNMPMethodsTranslations.get(config.value) | translate }}</ng-template>
<ng-template #RPCObjectRow>
<ng-container
*ngFor="let subConfig of config.value | keyvalue : originalOrder"

15
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss

@ -59,6 +59,7 @@
flex-direction: row-reverse;
align-items: center;
margin-right: 0;
flex: 0;
& > mat-icon {
margin-left: 15px;
@ -89,6 +90,20 @@
}
}
.template-name {
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
::ng-deep .mat-content {
align-items: center;
}
.mat-expansion-panel-header-title {
flex: 1;
margin: 0;
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts

@ -17,7 +17,8 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
ConnectorType,
RPCTemplate
RPCTemplate,
SNMPMethodsTranslations
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
@ -49,6 +50,7 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit {
public readonly originalOrder = (): number => 0;
public readonly isObject = (value: any) => isLiteralObject(value);
public readonly SNMPMethodsTranslations = SNMPMethodsTranslations;
constructor(private attributeService: AttributeService) {
}

36
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html

@ -368,38 +368,7 @@
{{ 'gateway.rpc.add-header' | translate }}
</button>
</fieldset>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="security">
<span class="fields-label">{{ 'gateway.rpc.security' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('security').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.security-name' | translate }}</span>
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span>
<span fxFlex="30px"></span>
</div>
<mat-divider></mat-divider>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('security'); let i = index">
<ng-container [formGroupName]="i">
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="securityName"/>
</mat-form-field>
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="value" placeholder="anonymous"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeHTTPSecurity(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</ng-container>
</div>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addHTTPSecurity()">
{{ 'gateway.rpc.add-security' | translate }}
</button>
</fieldset>
<tb-rest-connector-security [formControl]="commandForm.get('security')"></tb-rest-connector-security>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REQUEST">
<mat-form-field>
@ -478,9 +447,6 @@
</button>
</fieldset>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OPCUA_ASYNCIO">
<ng-container *ngTemplateOutlet="OPCUAForm"></ng-container>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OPCUA" #OPCUAForm>
<mat-form-field >
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label>

55
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<void>();
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();
}
}

1
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:

16
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<ConnectorType,
[ConnectorType.MODBUS, 'MODBUS'],
[ConnectorType.GRPC, 'GRPC'],
[ConnectorType.OPCUA, 'OPCUA'],
[ConnectorType.OPCUA_ASYNCIO, 'OPCUA ASYNCIO'],
[ConnectorType.BLE, 'BLE'],
[ConnectorType.REQUEST, 'REQUEST'],
[ConnectorType.CAN, 'CAN'],
@ -269,7 +267,7 @@ export const SNMPMethodsTranslations = new Map<SNMPMethods, string>([
[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<BrokerSecurityType, str
]
);
export enum RestSecurityType {
ANONYMOUS = 'anonymous',
BASIC = 'basic',
}
export const RestSecurityTypeTranslationsMap = new Map<RestSecurityType, string>(
[
[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 },

16
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,

20
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": {

1
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"
},

1
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"
},

Loading…
Cancel
Save