Browse Source

resolve conflicts

pull/11101/head
mpetrov 2 years ago
parent
commit
02495cedf9
  1. 199
      application/src/main/data/json/tenant/dashboards/gateways.json
  2. 50
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  3. 1
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html
  4. 3
      ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html
  5. 45
      ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts
  6. 46
      ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts
  7. 6
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  8. 14
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts
  9. 38
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  10. 8
      ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-transport-configuration.component.ts
  11. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.html
  12. 65
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html
  13. 29
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss
  14. 132
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts
  15. 21
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html
  16. 25
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts
  17. 3
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss
  18. 1211
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html
  19. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss
  20. 26
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  21. 104
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  22. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html
  23. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts
  24. 9
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html
  25. 15
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss
  26. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts
  27. 36
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html
  28. 55
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts
  29. 1
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts
  30. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  31. 4
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  32. 1
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  33. 2
      ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts
  34. 1
      ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html
  35. 11
      ui-ngx/src/app/modules/home/pages/widget/widget-type-autocomplete.component.html
  36. 4
      ui-ngx/src/app/modules/home/pages/widget/widget-type-autocomplete.component.ts
  37. 11
      ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html
  38. 3
      ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts
  39. 13
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html
  40. 17
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  41. 11
      ui-ngx/src/app/shared/components/entity/entity-list.component.html
  42. 4
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  43. 21
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  44. 1
      ui-ngx/src/assets/locale/locale.constant-lt_LT.json
  45. 1
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json

199
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,
@ -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": "<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,

50
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

1
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html

@ -186,6 +186,7 @@
<div fxFlex fxLayout="row" style="overflow: hidden; align-items: center;">
<span class="ellipsis">{{attribute.value | tbJson}}</span>
<tb-copy-button
*ngIf="attribute.value !== ''"
class="attribute-copy"
[disabled]="isLoading$ | async"
[copyText]="attribute.value | tbJson"

3
ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html

@ -54,7 +54,8 @@
formControlName="deviceProfileId"
[displayAllOnEmpty]="true"
(deviceProfileChanged)="deviceProfileChanged($event)"
[editProfileEnabled]="false">
[editProfileEnabled]="false"
[addNewProfile]="false">
</tb-device-profile-autocomplete>
<mat-form-field>
<mat-label translate>device.device-state</mat-label>

45
ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts

@ -15,7 +15,17 @@
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@ -27,7 +37,6 @@ import {
defaultRpcRequestSchema,
defaultRpcResponseSchema,
defaultTelemetrySchema,
DeviceProfileTransportConfiguration,
DeviceTransportType,
TransportPayloadType,
transportPayloadTypeTranslationMap,
@ -41,13 +50,20 @@ import { PowerMode } from '@home/components/profile/device/lwm2m/lwm2m-profile-c
selector: 'tb-coap-device-profile-transport-configuration',
templateUrl: './coap-device-profile-transport-configuration.component.html',
styleUrls: ['./coap-device-profile-transport-configuration.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CoapDeviceProfileTransportConfigurationComponent),
multi: true
}]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CoapDeviceProfileTransportConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CoapDeviceProfileTransportConfigurationComponent),
multi: true
}
]
})
export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy, Validator {
coapTransportDeviceTypes = Object.values(CoapTransportDeviceType);
coapTransportDeviceTypeTranslations = coapDeviceTypeTranslationMap;
@ -178,12 +194,15 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control
}
}
public validate(c: UntypedFormControl): ValidationErrors | null {
return (this.coapTransportConfigurationFormGroup.valid) ? null : {
valid: false,
};
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.coapTransportConfigurationFormGroup.valid) {
configuration = this.coapTransportConfigurationFormGroup.value;
configuration.type = DeviceTransportType.COAP;
}
const configuration = this.coapTransportConfigurationFormGroup.value;
configuration.type = DeviceTransportType.COAP;
this.propagateChange(configuration);
}
}

46
ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts

@ -15,7 +15,17 @@
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@ -26,13 +36,20 @@ import { deepClone } from '@core/utils';
selector: 'tb-device-profile-transport-configuration',
templateUrl: './device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileTransportConfigurationComponent),
multi: true
}]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileTransportConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DeviceProfileTransportConfigurationComponent),
multi: true,
}
]
})
export class DeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
export class DeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
deviceTransportType = DeviceTransportType;
@ -98,11 +115,16 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.deviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.deviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = this.transportType;
}
const configuration = this.deviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = this.transportType;
this.propagateChange(configuration);
}
public validate(c: UntypedFormControl): ValidationErrors | null {
return (this.deviceProfileTransportConfigurationFormGroup.valid) ? null : {
configuration: {
valid: false,
},
};
}
}

6
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -296,11 +296,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
}
private updateModel = (): void => {
let configuration: Lwm2mProfileConfigModels = null;
if (this.lwm2mDeviceProfileFormGroup.valid) {
configuration = this.configurationValue;
}
this.propagateChange(configuration);
this.propagateChange(this.configurationValue);
}
private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => {

14
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts

@ -31,7 +31,7 @@ import { distinctUntilChanged, filter, mergeMap, share, tap } from 'rxjs/operato
import { ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { Direction } from '@shared/models/page/sort-order';
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { isDefined, isDefinedAndNotNull, isObject, isString } from '@core/utils';
import { PageLink } from '@shared/models/page/page-link';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
@ -90,12 +90,14 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
objectsList: [this.objectsList],
objectLwm2m: ['']
});
this.lwm2mListFormGroup.valueChanges.subscribe((value) => {
let formValue = null;
if (this.lwm2mListFormGroup.valid) {
formValue = value.objectsList;
this.lwm2mListFormGroup.get('objectsList').valueChanges.subscribe((value) => {
if (!value.length || (value.length && isObject(value[0]))) {
let formValue = null;
if (this.lwm2mListFormGroup.valid) {
formValue = value;
}
this.propagateChange(formValue);
}
this.propagateChange(formValue);
});
}

38
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts

@ -17,10 +17,13 @@
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
@ -32,7 +35,6 @@ import {
defaultRpcRequestSchema,
defaultRpcResponseSchema,
defaultTelemetrySchema,
DeviceProfileTransportConfiguration,
DeviceTransportType,
MqttDeviceProfileTransportConfiguration,
TransportPayloadType,
@ -46,13 +48,20 @@ import { takeUntil } from 'rxjs/operators';
selector: 'tb-mqtt-device-profile-transport-configuration',
templateUrl: './mqtt-device-profile-transport-configuration.component.html',
styleUrls: ['./mqtt-device-profile-transport-configuration.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MqttDeviceProfileTransportConfigurationComponent),
multi: true
}]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MqttDeviceProfileTransportConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MqttDeviceProfileTransportConfigurationComponent),
multi: true
}
]
})
export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy, Validator {
transportPayloadTypes = Object.keys(TransportPayloadType);
@ -172,12 +181,15 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
}
}
public validate(c: UntypedFormControl): ValidationErrors | null {
return (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) ? null : {
valid: false,
};
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue();
configuration.type = DeviceTransportType.MQTT;
}
const configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue();
configuration.type = DeviceTransportType.MQTT;
this.propagateChange(configuration);
}

8
ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-transport-configuration.component.ts

@ -27,7 +27,6 @@ import {
} from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceProfileTransportConfiguration,
DeviceTransportType,
SnmpDeviceProfileTransportConfiguration
} from '@shared/models/device.models';
@ -125,11 +124,8 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements OnInit,
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue();
configuration.type = DeviceTransportType.SNMP;
}
const configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue();
configuration.type = DeviceTransportType.SNMP;
this.propagateChange(configuration);
}

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/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">
@ -62,7 +62,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));
}
}

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

@ -44,7 +44,7 @@
<div class="fixed-title-width tb-required" translate>gateway.name</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="name" placeholder="{{ 'gateway.set' | translate }}"/>
<input matInput autocomplete="off" name="value" formControlName="name" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
@ -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()
}
}

3
ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss

@ -33,8 +33,7 @@
}
.mat-mdc-dialog-content {
max-height: 75vh;
height: 75vh;
height: 670px;
}
.ellipsis-chips-container {

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

26
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">
@ -181,18 +181,36 @@
<div class="fixed-title-width tb-required" translate>gateway.name</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="name" placeholder="{{ 'gateway.set' | translate }}"/>
<input matInput autocomplete="off" name="value" formControlName="name" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.name-required') | translate"
*ngIf="connectorForm.get('name').hasError('required') && connectorForm.get('name').touched"
[matTooltip]="(connectorForm.get('name').hasError('duplicateName') ?
'gateway.connector-duplicate-name' : 'gateway.name-required') | translate"
*ngIf="(connectorForm.get('name').hasError('required') && connectorForm.get('name').touched) ||
connectorForm.get('name').hasError('duplicateName')"
class="tb-error">
warning
</mat-icon>
</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

@ -75,7 +75,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 { distinctUntilChanged, filter, take, takeUntil, tap } from 'rxjs/operators';
import { distinctUntilChanged, filter, debounceTime, take, takeUntil, tap } from 'rxjs/operators';
import { ErrorStateMatcher } from '@angular/material/core';
import { PageData } from '@shared/models/page/page-data';
import { MatTab } from '@angular/material/tabs';
@ -161,6 +161,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,
@ -193,6 +194,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
basicConfig: this.fb.group({})
});
this.connectorForm.disable();
this.observeAttributeChange();
}
ngAfterViewInit(): void {
@ -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) {
@ -519,31 +533,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 {
@ -589,6 +584,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

@ -247,7 +247,6 @@ export enum ConnectorType {
MODBUS = 'modbus',
GRPC = 'grpc',
OPCUA = 'opcua',
OPCUA_ASYNCIO = 'opcua_asyncio',
BLE = 'ble',
REQUEST = 'request',
CAN = 'can',
@ -267,7 +266,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'],
@ -391,7 +389,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']
])
@ -494,6 +492,18 @@ export const SecurityTypeTranslationsMap = new Map<SecurityType, string>(
]
);
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 },

4
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -98,6 +98,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';
import { GatewayHelpLinkPipe } from '@home/pipes/public-api';
import {
@ -185,6 +188,7 @@ import {
RpcWidgetsModule,
HomePageWidgetsModule,
SharedHomeComponentsModule,
RestConnectorSecurityComponent,
GatewayHelpLinkPipe,
BrokerConfigControlComponent,
WorkersConfigControlComponent,

1
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -60,7 +60,6 @@
<tb-device-profile-autocomplete
required
addNewProfile
showCreateNewButton
selectDefaultProfile
formControlName="deviceProfileId"
(deviceProfileChanged)="deviceProfileChanged($event)">

2
ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts

@ -89,7 +89,7 @@ export class AttributeDatasource implements DataSource<AttributeData> {
pageLink: PageLink): Observable<PageData<AttributeData>> {
return this.getAllAttributes(entityId, attributesScope).pipe(
map((data) => {
const filteredData = data.filter(attrData => attrData.lastUpdateTs !== 0 && attrData.value !== '');
const filteredData = data.filter(attrData => attrData.lastUpdateTs !== 0);
return pageLink.filterData(filteredData);
})
);

1
ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html

@ -19,6 +19,7 @@
subscriptSizing="dynamic"
[ngModel]="entitiesTableConfig.componentsData.assetProfileId"
(ngModelChange)="assetProfileChanged($event)"
[addNewProfile]="false"
[displayAllOnEmpty]="true"
[editProfileEnabled]="false">
</tb-asset-profile-autocomplete>

11
ui-ngx/src/app/modules/home/pages/widget/widget-type-autocomplete.component.html

@ -45,9 +45,16 @@
</div>
</mat-option>
<mat-option *ngIf="!(filteredWidgetTypes | async)?.length" [value]="null">
<span>
<div (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span>{{ 'widget.no-widgets-text' | translate }}</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('widget.no-widgets-matching', {entity: searchText}) | async }}
</span>
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error>

4
ui-ngx/src/app/modules/home/pages/widget/widget-type-autocomplete.component.ts

@ -211,4 +211,8 @@ export class WidgetTypeAutocompleteComponent implements ControlValueAccessor, On
}, 0);
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
}

11
ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html

@ -57,9 +57,16 @@
<span [innerHTML]="dashboard.title | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredDashboards | async)?.length" [value]="null">
<span>
<div (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span>{{ 'dashboard.no-dashboards-text' | translate }}</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('dashboard.no-dashboards-matching', {entity: searchText}) | async }}
</span>
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="!inlineField">

3
ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts

@ -276,4 +276,7 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI
}, 0);
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
}

13
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html

@ -40,9 +40,16 @@
<span [innerHTML]="entity.name | highlight:searchText:true:'ig'"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<span>
{{ translate.get(noEntitiesMatchingText, {entity: searchText}) | async }}
</span>
<div (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span>{{ notFoundEntities | translate }}</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get(noEntitiesMatchingText, {entity: searchText}) | async }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="selectEntityFormGroup.get('entity').hasError('required')">

17
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -64,6 +64,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
entityText: string;
noEntitiesMatchingText: string;
notFoundEntities = 'entity.no-entities-text';
entityRequiredText: string;
@ -201,63 +202,75 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
this.entityText = 'asset.asset';
this.noEntitiesMatchingText = 'asset.no-assets-matching';
this.entityRequiredText = 'asset.asset-required';
this.notFoundEntities = 'asset.no-assets-text';
break;
case EntityType.DEVICE:
this.entityText = 'device.device';
this.noEntitiesMatchingText = 'device.no-devices-matching';
this.entityRequiredText = 'device.device-required';
this.notFoundEntities = 'device.no-devices-text';
break;
case EntityType.EDGE:
this.entityText = 'edge.edge';
this.noEntitiesMatchingText = 'edge.no-edges-matching';
this.entityRequiredText = 'edge.edge-required';
this.notFoundEntities = 'edge.no-edges-text';
break;
case EntityType.ENTITY_VIEW:
this.entityText = 'entity-view.entity-view';
this.noEntitiesMatchingText = 'entity-view.no-entity-views-matching';
this.entityRequiredText = 'entity-view.entity-view-required';
this.notFoundEntities = 'entity-view.no-entity-views-text';
break;
case EntityType.RULE_CHAIN:
this.entityText = 'rulechain.rulechain';
this.noEntitiesMatchingText = 'rulechain.no-rulechains-matching';
this.entityRequiredText = 'rulechain.rulechain-required';
this.notFoundEntities = 'rulechain.no-rulechains-text';
break;
case EntityType.TENANT:
case AliasEntityType.CURRENT_TENANT:
this.entityText = 'tenant.tenant';
this.noEntitiesMatchingText = 'tenant.no-tenants-matching';
this.entityRequiredText = 'tenant.tenant-required';
this.notFoundEntities = 'tenant.no-tenants-text';
break;
case EntityType.CUSTOMER:
this.entityText = 'customer.customer';
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.customer-required';
this.notFoundEntities = 'customer.no-customers-text';
break;
case EntityType.USER:
case AliasEntityType.CURRENT_USER:
this.entityText = 'user.user';
this.noEntitiesMatchingText = 'user.no-users-matching';
this.entityRequiredText = 'user.user-required';
this.notFoundEntities = 'user.no-users-text';
break;
case EntityType.DASHBOARD:
this.entityText = 'dashboard.dashboard';
this.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
this.entityRequiredText = 'dashboard.dashboard-required';
this.notFoundEntities = 'dashboard.no-dashboards-text';
break;
case EntityType.ALARM:
this.entityText = 'alarm.alarm';
this.noEntitiesMatchingText = 'alarm.no-alarms-matching';
this.entityRequiredText = 'alarm.alarm-required';
this.notFoundEntities = 'alarm.no-alarms-prompt';
break;
case EntityType.QUEUE_STATS:
this.entityText = 'queue-statistics.queue-statistics';
this.noEntitiesMatchingText = 'queue-statistics.no-queue-statistics-matching';
this.entityRequiredText = 'queue-statistics.queue-statistics-required';
this.notFoundEntities = 'queue-statistics.no-queue-statistics-text';
break;
case AliasEntityType.CURRENT_CUSTOMER:
this.entityText = 'customer.default-customer';
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.default-customer-required';
this.notFoundEntities = 'customer.no-customers-text';
break;
case AliasEntityType.CURRENT_USER_OWNER:
const authUser = getCurrentAuthUser(this.store);
@ -375,6 +388,10 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
));
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
clear() {
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true});
setTimeout(() => {

11
ui-ngx/src/app/shared/components/entity/entity-list.component.html

@ -42,9 +42,16 @@
<span [innerHTML]="entity.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<span>
<div (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span>{{ 'entity.no-entities-text' | translate }}</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('entity.no-entities-matching', {entity: searchText}) | async }}
</span>
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-hint *ngIf="hint">

4
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -257,4 +257,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}, 0);
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
}

21
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2177,6 +2177,7 @@
"add-entity-type": "Add entity type",
"enter-entity-type": "Enter entity type",
"no-entities-matching": "No entities matching '{{entity}}' were found.",
"no-entities-text": "No entities found",
"no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
"name-starts-with": "Name expression",
"help-text": "Use '%' according to need: '%entity_name_contains%', '%entity_name_ends', 'entity_starts_with'.",
@ -2813,8 +2814,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",
@ -3272,18 +3273,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",
@ -6122,8 +6123,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