diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 42c4f199b5..11813ae878 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, @@ -880,7 +880,7 @@ "isConnectorLog": true, "connectorLogState": "connector_logs" }, - "title": "Gateway Logs", + "title": "${entityName} Logs", "showTitleIcon": false, "dropShadow": true, "enableFullscreen": true, @@ -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/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html index 2a3108b8b4..c3fe478ef2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table.component.html @@ -28,7 +28,7 @@
-
gateway.device-info.name
+
gateway.device-info.name
@@ -61,7 +61,7 @@
-
gateway.device-info.profile-name
+
gateway.device-info.profile-name
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html new file mode 100644 index 0000000000..afabe53499 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html @@ -0,0 +1,65 @@ + +
+
+
gateway.security
+ + + {{ SecurityTypeTranslationsMap.get(type) | translate }} + + +
+ +
+
gateway.username
+
+ + + + warning + + +
+
+
+
gateway.password
+
+ + + + warning + +
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss new file mode 100644 index 0000000000..f014f56588 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + height: 100%; + display: block; + margin-bottom: 10px; + + .fields-label { + font-weight: 500; + } + + .hide-toggle { + display: none; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts new file mode 100644 index 0000000000..d00813bfd6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts @@ -0,0 +1,132 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + OnDestroy, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { + noLeadTrailSpacesRegex, + RestSecurityType, + RestSecurityTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'tb-rest-connector-security', + templateUrl: './rest-connector-security.component.html', + styleUrls: ['./rest-connector-security.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RestConnectorSecurityComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RestConnectorSecurityComponent), + multi: true + } + ], + standalone: true, + imports: [ + SharedModule, + CommonModule, + ] +}) +export class RestConnectorSecurityComponent implements ControlValueAccessor, Validator, OnDestroy { + BrokerSecurityType = RestSecurityType; + securityTypes: RestSecurityType[] = Object.values(RestSecurityType); + SecurityTypeTranslationsMap = RestSecurityTypeTranslationsMap; + securityFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + private propagateChange = (_: any) => {}; + + constructor(private fb: FormBuilder) { + this.securityFormGroup = this.fb.group({ + type: [RestSecurityType.ANONYMOUS, []], + username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + password: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + }); + this.observeSecurityForm(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + writeValue(deviceInfo: any): void { + if (!deviceInfo.type) { + deviceInfo.type = RestSecurityType.ANONYMOUS; + } + this.securityFormGroup.reset(deviceInfo); + this.updateView(deviceInfo); + } + + validate(): ValidationErrors | null { + return this.securityFormGroup.valid ? null : { + securityForm: { valid: false } + }; + } + + private updateView(value: any): void { + this.propagateChange(value); + } + + private updateValidators(type: RestSecurityType): void { + if (type === RestSecurityType.BASIC) { + this.securityFormGroup.get('username').enable({emitEvent: false}); + this.securityFormGroup.get('password').enable({emitEvent: false}); + } else { + this.securityFormGroup.get('username').disable({emitEvent: false}); + this.securityFormGroup.get('password').disable({emitEvent: false}); + } + } + + private observeSecurityForm(): void { + this.securityFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => this.updateView(value)); + + this.securityFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => this.updateValidators(type)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html index ec1fa9a87f..d995136b27 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html @@ -44,7 +44,7 @@
gateway.name
- +
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
gateway.remote-logging-level
@@ -68,7 +84,8 @@
-
+
{{ 'gateway.fill-connector-defaults' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts index 98261a8e57..7cdfbf5808 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -33,6 +33,7 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { Subject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; +import { takeUntil, tap } from "rxjs/operators"; @Component({ selector: 'tb-add-connector-dialog', @@ -40,7 +41,7 @@ import { ResourcesService } from '@core/services/resources.service'; styleUrls: ['./add-connector-dialog.component.scss'], providers: [], }) -export class AddConnectorDialogComponent extends DialogComponent> implements OnDestroy { +export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { connectorForm: UntypedFormGroup; @@ -66,9 +67,15 @@ export class AddConnectorDialogComponent extends DialogComponent { + const useDefaultControl = this.connectorForm.get('useDefaults'); + if (type === ConnectorType.GRPC || type === ConnectorType.CUSTOM) { + useDefaultControl.setValue(false); + } else if (!useDefaultControl.value) { + useDefaultControl.setValue(true); + } + }), + takeUntil(this.destroy$), + ).subscribe() + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss index db98bc07cb..d002a0fc87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss @@ -33,7 +33,6 @@ } .mat-mdc-dialog-content { - max-height: 670px; height: 670px; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html index 8aeaeef2ed..40e557bbbb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html @@ -20,50 +20,52 @@ -
-
-
- - {{ 'gateway.remote-configuration' | translate }} - -
-
- - {{ 'gateway.remote-shell' | translate }} - -
-
- - gateway.thingsboard-host - - info_outlined - - - {{ 'gateway.thingsboard-host-required' | translate }} - - - - gateway.thingsboard-port - - - {{ 'gateway.thingsboard-port-required' | translate }} - - - {{ 'gateway.thingsboard-port-min' | translate }} - - - {{ 'gateway.thingsboard-port-max' | translate }} - - - {{ 'gateway.thingsboard-port-pattern' | translate }} - - info_outlined - - + +
+
+
+ + {{ 'gateway.remote-configuration' | translate }} + +
+
+ + {{ 'gateway.remote-shell' | translate }} + +
+
+ + gateway.thingsboard-host + + info_outlined + + + {{ 'gateway.thingsboard-host-required' | translate }} + + + + gateway.thingsboard-port + + + {{ 'gateway.thingsboard-port-required' | translate }} + + + {{ 'gateway.thingsboard-port-min' | translate }} + + + {{ 'gateway.thingsboard-port-max' | translate }} + + + {{ 'gateway.thingsboard-port-pattern' | translate }} + + info_outlined + + +
@@ -93,664 +95,677 @@ matTooltip="{{ 'gateway.hints.token' | translate }}">info_outlined -
- - security.clientId - - - {{ 'security.clientId-required' | 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.min-pack-send-delay + + + {{ 'gateway.min-pack-send-delay-required' | translate }} - - {{ 'gateway.grpc-max-pings-without-data-min' | translate }} + + {{ 'gateway.min-pack-send-delay-min' | translate }} - - {{ 'gateway.grpc-max-pings-without-data-pattern' | translate }} - - - - gateway.grpc-min-ping-interval-without-data - info_outlined + matTooltip="{{ 'gateway.hints.minimal-pack-delay' | translate }}">info_outlined - - {{ 'gateway.grpc-min-ping-interval-without-data-required' | translate }} + + + gateway.mqtt-qos + + + {{ 'gateway.mqtt-qos-required' | translate }} - - {{ 'gateway.grpc-min-ping-interval-without-data-min' | translate }} + + {{ 'gateway.mqtt-qos-range' | translate }} - - {{ 'gateway.grpc-min-ping-interval-without-data-pattern' | translate }} + + {{ 'gateway.mqtt-qos-range' | translate }} + info_outlined +
- -
-
-
- -
-
- - {{ 'gateway.statistics.statistics' | translate }} - - - gateway.statistics.send-period - - - {{ 'gateway.statistics.send-period-required' | translate }} - - - {{ 'gateway.statistics.send-period-min' | translate }} - - - {{ 'gateway.statistics.send-period-pattern' | translate }} - - -
-
-
gateway.statistics.commands
-
gateway.hints.commands
- -
-
-
- - gateway.statistics.attribute-name - - - {{ 'gateway.statistics.attribute-name-required' | translate }} - - info_outlined - - - - gateway.statistics.timeout - - - {{ 'gateway.statistics.timeout-required' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - {{ 'gateway.statistics.timeout-pattern' | translate }} - - info_outlined - - -
- - gateway.statistics.command - - - {{ 'gateway.statistics.command-required' | translate }} - - - {{ 'gateway.statistics.command-pattern' | translate }} - - info_outlined - - -
- -
- -
-
-
-
- -
-
-
- - {{ 'gateway.checking-device-activity' | translate }} - -
-
- - gateway.inactivity-timeout-seconds - + + gateway.statistics.check-connectors-configuration + - {{ 'gateway.inactivity-timeout-seconds-required' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('required')"> + {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - {{ 'gateway.inactivity-timeout-seconds-min' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('min')"> + {{ 'gateway.statistics.check-connectors-configuration-min' | translate }} - {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} + *ngIf="gatewayConfigGroup.get('thingsboard.checkConnectorsConfigurationInSeconds').hasError('pattern')"> + {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} - info_outlined - - - gateway.inactivity-check-period-seconds - - - {{ 'gateway.inactivity-check-period-seconds-required' | translate }} - - - {{ 'gateway.inactivity-check-period-seconds-min' | translate }} - - - {{ 'gateway.inactivity-check-period-seconds-pattern' | translate }} - - info_outlined - - -
-
-
-
gateway.advanced
-
- - gateway.min-pack-send-delay - - - {{ 'gateway.min-pack-send-delay-required' | translate }} - - - {{ 'gateway.min-pack-send-delay-min' | translate }} - - info_outlined - - - - gateway.mqtt-qos - - - {{ 'gateway.mqtt-qos-required' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - info_outlined - - -
- - gateway.statistics.check-connectors-configuration - - - {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-min' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} - - +
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss index f7562c748e..9d72d1d759 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss @@ -55,7 +55,7 @@ } .statistics-container { - width: 50%; + width: 100%; .command-container { width: 100%; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 3a7f5403b9..eb7afe0cc5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -46,7 +46,7 @@ + (click)="$event.stopPropagation(); onEnableConnector(attribute)"> @@ -178,18 +178,36 @@
gateway.name
- + warning
+
+
gateway.connectors-table-class
+
+ + + +
+
+
+
gateway.connectors-table-key
+
+ + + +
+
gateway.logs-configuration
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 4f1b6b72a9..66d33b9a06 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -62,7 +62,7 @@ import { } from './gateway-widget.models'; import { MatDialog } from '@angular/material/dialog'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; -import { takeUntil } from 'rxjs/operators'; +import { debounceTime, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; @@ -148,6 +148,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private destroy$ = new Subject(); private subscription: IWidgetSubscription; + private attributeUpdateSubject = new Subject(); constructor(protected store: Store, private fb: FormBuilder, @@ -180,6 +181,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie basicConfig: this.fb.group({}) }); this.connectorForm.disable(); + + this.observeAttributeChange(); } get portErrorTooltip(): string { @@ -238,12 +241,23 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.dataSource.sort = this.sort; this.dataSource.sortingDataAccessor = (data: AttributeData, sortHeaderId: string) => { - if (sortHeaderId === 'syncStatus') { - return this.isConnectorSynced(data) ? 1 : 0; - } else if (sortHeaderId === 'enabled') { - return this.activeConnectors.includes(data.key) ? 1 : 0; + switch (sortHeaderId) { + case 'syncStatus': + return this.isConnectorSynced(data) ? 1 : 0; + + case 'enabled': + return this.activeConnectors.includes(data.key) ? 1 : 0; + + case 'errors': + const errors = this.getErrorsCount(data); + if (typeof errors === 'string') { + return this.sort.direction.toUpperCase() === Direction.DESC ? -1 : Infinity; + } + return errors; + + default: + return data[sortHeaderId] || data.value[sortHeaderId]; } - return data[sortHeaderId] || data.value[sortHeaderId]; }; if (this.device) { @@ -524,31 +538,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } - enableConnector(attribute: AttributeData): void { - const wasEnabled = this.activeConnectors.includes(attribute.key); - const scopeOld = wasEnabled ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; - const scopeNew = !wasEnabled ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; + onEnableConnector(attribute: AttributeData): void { attribute.value.ts = new Date().getTime(); - const tasks = [this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, [{ - key: 'active_connectors', - value: this.activeConnectors - }]), this.attributeService.saveEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, [{ - key: 'inactive_connectors', - value: this.inactiveConnectors - }]), this.attributeService.deleteEntityAttributes(this.device, scopeOld, [attribute]), - this.attributeService.saveEntityAttributes(this.device, scopeNew, [attribute])]; - if (wasEnabled) { - const index = this.activeConnectors.indexOf(attribute.key); - this.activeConnectors.splice(index, 1); - this.inactiveConnectors.push(attribute.key); - } else { - const index = this.inactiveConnectors.indexOf(attribute.key); - this.inactiveConnectors.splice(index, 1); - this.activeConnectors.push(attribute.key); - } - forkJoin(tasks).subscribe(_ => { - this.updateData(true); - }); + + this.updateActiveConnectorKeys(attribute.key); + + this.attributeUpdateSubject.next(attribute); } getErrorsCount(attribute: AttributeData): string { @@ -599,6 +594,55 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.get(formControlName)?.patchValue('tb_gw_' + generateSecret(5)); } + private observeAttributeChange(): void { + this.attributeUpdateSubject.pipe( + debounceTime(300), + tap((attribute: AttributeData) => this.executeAttributeUpdates(attribute)), + takeUntil(this.destroy$), + ).subscribe(); + } + + private updateActiveConnectorKeys(key: string): void { + const wasEnabled = this.activeConnectors.includes(key); + if (wasEnabled) { + const index = this.activeConnectors.indexOf(key); + if (index !== -1) this.activeConnectors.splice(index, 1); + this.inactiveConnectors.push(key); + } else { + const index = this.inactiveConnectors.indexOf(key); + if (index !== -1) this.inactiveConnectors.splice(index, 1); + this.activeConnectors.push(key); + } + } + + private executeAttributeUpdates(attribute: AttributeData): void { + forkJoin(this.getAttributeExecutionTasks(attribute)) + .pipe( + take(1), + tap(() => this.updateData(true)), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + private getAttributeExecutionTasks(attribute: AttributeData): Observable[] { + const isActive = this.activeConnectors.includes(attribute.key); + const scopeOld = isActive ? AttributeScope.SERVER_SCOPE : AttributeScope.SHARED_SCOPE; + const scopeNew = isActive ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; + + return [ + this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, [{ + key: 'active_connectors', + value: this.activeConnectors + }]), + this.attributeService.saveEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, [{ + key: 'inactive_connectors', + value: this.inactiveConnectors + }]), + this.attributeService.deleteEntityAttributes(this.device, scopeOld, [attribute]), + this.attributeService.saveEntityAttributes(this.device, scopeNew, [attribute])]; + } + private onDataUpdateError(e: any): void { const exceptionData = this.utils.parseException(e); let errorText = exceptionData.name; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html index d36a4c0a51..098a8ce5e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html @@ -21,7 +21,7 @@ [active]="activeLink.name === link.name"> {{ link.name }} - @@ -31,7 +31,7 @@ - {{ 'widgets.gateway.status' | translate }} + {{ 'widgets.gateway.level' | translate }} {{ attribute.status }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts index 0607268edc..d029279c2b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts @@ -164,6 +164,10 @@ export class GatewayLogsComponent implements AfterViewInit { } } + trackByLogTs(_: number, log: GatewayLogData): number { + return log.ts; + } + private changeSubscription() { if (this.ctx.datasources && this.ctx.datasources[0].entity && this.ctx.defaultSubscription.options.datasources) { this.ctx.defaultSubscription.options.datasources[0].dataKeys = [{ @@ -178,5 +182,4 @@ export class GatewayLogsComponent implements AfterViewInit { }; } } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html index c0f6997a36..2fdfcfbf0a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -18,8 +18,8 @@
{{ 'gateway.rpc.templates-title' | translate }}
- - {{template.name}} + + {{template.name}} -
- {{ 'gateway.rpc.security' | translate }} -
-
- {{ 'gateway.rpc.security-name' | translate }} - {{ 'gateway.rpc.value' | translate }} - -
- -
- - - - - - - - delete - - -
-
- -
+ @@ -478,9 +447,6 @@ - - - {{ 'gateway.rpc.method' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 9838fd2df2..c515cbc201 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, FormArray, @@ -53,6 +53,8 @@ import { } from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; +import { takeUntil, tap } from "rxjs/operators"; +import { Subject } from "rxjs"; @Component({ selector: 'tb-gateway-service-rpc-connector', @@ -66,7 +68,7 @@ import { deepClone } from '@core/utils'; } ] }) -export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor { +export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, ControlValueAccessor { @Input() connectorType: ConnectorType; @@ -105,6 +107,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue private propagateChange = (v: any) => { } + private destroy$ = new Subject(); constructor(private fb: FormBuilder, private dialog: MatDialog,) { @@ -114,17 +117,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); this.commandForm.valueChanges.subscribe(value => { const httpHeaders = {}; - const security = {}; switch (this.connectorType) { case ConnectorType.REST: value.httpHeaders.forEach(data => { httpHeaders[data.headerName] = data.value; }) value.httpHeaders = httpHeaders; - value.security.forEach(data => { - security[data.securityName] = data.value; - }) - value.security = security; break; case ConnectorType.REQUEST: value.httpHeaders.forEach(data => { @@ -138,6 +136,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue } }); this.isMQTTWithResponse = this.fb.control(false); + this.observeMQTTWithResponse(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } connectorParamsFormGroupByType(type: ConnectorType): FormGroup { @@ -148,7 +152,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue formGroup = this.fb.group({ methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], + responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], responseTimeout: [null, [Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], }) @@ -252,7 +256,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), - security: this.fb.array([]) + security: [{}, [Validators.required]] }) break; case ConnectorType.REQUEST: @@ -269,7 +273,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue }) break; case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: formGroup = this.fb.group({ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], arguments: this.fb.array([]), @@ -312,22 +315,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue oidsFA.removeAt(index); } - addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) { - const securityFA = this.commandForm.get('security') as FormArray; - const formGroup = this.fb.group({ - securityName: [value.securityName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }) - if (securityFA) { - securityFA.push(formGroup, {emitEvent: false}); - } - } - - removeHTTPSecurity(index: number) { - const oidsFA = this.commandForm.get('security') as FormArray; - oidsFA.removeAt(index); - } - getFormArrayControls(path: string) { return (this.commandForm.get(path) as FormArray).controls as FormControl[]; } @@ -402,18 +389,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue break; case ConnectorType.REST: this.clearFromArrayByName("httpHeaders"); - this.clearFromArrayByName("security"); - value.security && Object.entries(value.security).forEach(securityHeader => { - this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string}) - }) - delete value.security; value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) }) delete value.httpHeaders; break; case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: this.clearFromArrayByName("arguments"); value.arguments.forEach(value => { this.addOCPUAArguments(value) @@ -424,4 +405,14 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue this.commandForm.patchValue(value, {onlySelf: false}); } } + + private observeMQTTWithResponse(): void { + this.isMQTTWithResponse.valueChanges.pipe( + tap((isActive: boolean) => { + const responseControl = this.commandForm.get('responseTopicExpression'); + isActive ? responseControl.enable() : responseControl.disable(); + }), + takeUntil(this.destroy$), + ).subscribe(); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index b5cde980f9..dadd1a38d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -150,7 +150,6 @@ export class GatewayServiceRPCComponent implements OnInit { case ConnectorType.BACNET: case ConnectorType.CAN: case ConnectorType.OPCUA: - case ConnectorType.OPCUA_ASYNCIO: return params.method; case ConnectorType.BLE: case ConnectorType.OCPP: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 0e143cff36..095d14ae76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -125,7 +125,6 @@ export enum ConnectorType { MODBUS = 'modbus', GRPC = 'grpc', OPCUA = 'opcua', - OPCUA_ASYNCIO = 'opcua_asyncio', BLE = 'ble', REQUEST = 'request', CAN = 'can', @@ -145,7 +144,6 @@ export const GatewayConnectorDefaultTypesTranslatesMap = new Map([ [SNMPMethods.TABLE, 'gateway.rpc.table'], [SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'], [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], - [SNMPMethods.BULKGET, 'gateway.rpc.bul-kget'], + [SNMPMethods.BULKGET, 'gateway.rpc.bulk-get'], [SNMPMethods.WALKS, 'gateway.rpc.walk'] ]) @@ -360,6 +358,18 @@ export const BrokerSecurityTypeTranslationsMap = new Map( + [ + [RestSecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], + [RestSecurityType.BASIC, 'gateway.broker.security-types.basic'], + ] +); + export const MqttVersions = [ { name: 3.1, value: 3 }, { name: 3.11, value: 4 }, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index a473df40bb..04f3e5b996 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 @@ -102,6 +102,9 @@ import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/rad import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobile-app-qrcode-widget.component'; import { LabelCardWidgetComponent } from '@home/components/widget/lib/cards/label-card-widget.component'; import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards/label-value-card-widget.component'; +import { + RestConnectorSecurityComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component'; @NgModule({ declarations: @@ -172,13 +175,14 @@ import { LabelValueCardWidgetComponent } from '@home/components/widget/lib/cards LabelCardWidgetComponent, LabelValueCardWidgetComponent ], - 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 73e7e74e53..b1b17ca3aa 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2806,8 +2806,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", @@ -3238,18 +3238,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", @@ -6081,8 +6081,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" },