diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json index 7a21e86761..a268823f36 100644 --- a/application/src/main/data/json/demo/dashboards/firmware.json +++ b/application/src/main/data/json/demo/dashboards/firmware.json @@ -279,7 +279,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1142,7 +1142,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1448,7 +1448,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1754,7 +1754,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -2060,7 +2060,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], diff --git a/application/src/main/data/json/demo/dashboards/software.json b/application/src/main/data/json/demo/dashboards/software.json index eea81e5368..ebc4e6cd7d 100644 --- a/application/src/main/data/json/demo/dashboards/software.json +++ b/application/src/main/data/json/demo/dashboards/software.json @@ -279,7 +279,7 @@ "name": "Edit software", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1142,7 +1142,7 @@ "name": "Edit software", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1448,7 +1448,7 @@ "name": "Edit software", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -1754,7 +1754,7 @@ "name": "Edit software", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], @@ -2060,7 +2060,7 @@ "name": "Edit software", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 223722fee1..94f80322ae 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -160,7 +160,7 @@ "name": "Add", "icon": "add", "type": "customPretty", - "customHtml": "
\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n \n \n Low humidity alarm\n \n \n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", "customCss": ".add-entity-form{\n width: 300px;\n}\n", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], @@ -180,7 +180,7 @@ "name": "Edit", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", "customCss": ".edit-entity-form{\n width: 300px;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.temperatureAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n // }\n // if(vm.attributes.humidityAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], diff --git a/application/src/main/data/json/edge/instructions/install/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md index bcc1967dfd..86c5acad56 100644 --- a/application/src/main/data/json/edge/instructions/install/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -56,13 +56,13 @@ sudo yum update sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Install packages sudo yum -y install epel-release yum-utils -sudo yum-config-manager --enable pgdg15 -sudo yum install postgresql15-server postgresql15 +sudo yum-config-manager --enable pgdg16 +sudo yum install postgresql16-server postgresql16 postgresql16-contrib # Initialize your PostgreSQL DB -sudo /usr/pgsql-15/bin/postgresql-15-setup initdb -sudo systemctl start postgresql-15 +sudo /usr/pgsql-16/bin/postgresql-16-setup initdb +sudo systemctl start postgresql-16 # Optional: Configure PostgreSQL to start on boot -sudo systemctl enable --now postgresql-15 +sudo systemctl enable --now postgresql-16 {:copy-code} ``` @@ -74,12 +74,12 @@ sudo systemctl enable --now postgresql-15 sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Install packages sudo dnf -qy module disable postgresql -sudo dnf -y install postgresql15 postgresql15-server +sudo dnf -y install postgresql16 postgresql16-server postgresql16-contrib # Initialize your PostgreSQL DB -sudo /usr/pgsql-15/bin/postgresql-15-setup initdb -sudo systemctl start postgresql-15 +sudo /usr/pgsql-16/bin/postgresql-16-setup initdb +sudo systemctl start postgresql-16 # Optional: Configure PostgreSQL to start on boot -sudo systemctl enable --now postgresql-15 +sudo systemctl enable --now postgresql-16 {:copy-code} ``` @@ -101,7 +101,7 @@ After configuring the password, edit the pg_hba.conf to use MD5 authentication w Edit pg_hba.conf file: ```bash -sudo nano /var/lib/pgsql/15/data/pg_hba.conf +sudo nano /var/lib/pgsql/16/data/pg_hba.conf {:copy-code} ``` @@ -121,7 +121,7 @@ host all all 127.0.0.1/32 md5 Finally, you should restart the PostgreSQL service to initialize the new configuration: ```bash -sudo systemctl restart postgresql-15.service +sudo systemctl restart postgresql-16.service {:copy-code} ``` diff --git a/application/src/main/data/json/edge/instructions/install/docker/instructions.md b/application/src/main/data/json/edge/instructions/install/docker/instructions.md index 9910124b3e..23f484f2ec 100644 --- a/application/src/main/data/json/edge/instructions/install/docker/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/docker/instructions.md @@ -38,7 +38,7 @@ services: ${EXTRA_HOSTS} postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index fe9601443f..992b5e2ee2 100644 --- a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -49,7 +49,7 @@ echo "deb http://apt.postgresql.org/pub/repos/apt/ ${RELEASE}"-pgdg main | sudo # install and launch the postgresql service: sudo apt update -sudo apt -y install postgresql-15 +sudo apt -y install postgresql-16 sudo service postgresql start {:copy-code} ``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md index d922fa9155..a594ebe4e7 100644 --- a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md @@ -21,7 +21,7 @@ services: entrypoint: upgrade-tb-edge.sh postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/application/src/main/data/json/system/widget_types/asset_admin_table.json b/application/src/main/data/json/system/widget_types/asset_admin_table.json index cd373b9635..10a96e0043 100644 --- a/application/src/main/data/json/system/widget_types/asset_admin_table.json +++ b/application/src/main/data/json/system/widget_types/asset_admin_table.json @@ -18,7 +18,7 @@ "dataKeySettingsDirective": "tb-entities-table-key-settings", "hasBasicMode": true, "basicModeDirective": "tb-entities-table-basic-config", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"entitiesTitle\":\"Asset admin table\",\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"useRowStyleFunction\":false},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6985800247727296,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.10073938422359707,\"funcBody\":\"return 'Device';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]},\"configMode\":\"basic\",\"titleFont\":null,\"titleColor\":null}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"entitiesTitle\":\"Asset admin table\",\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"useRowStyleFunction\":false},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6985800247727296,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.10073938422359707,\"funcBody\":\"return 'Device';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]},\"configMode\":\"basic\",\"titleFont\":null,\"titleColor\":null}" }, "externalId": null, "tags": [ diff --git a/application/src/main/data/json/system/widget_types/basic_gpio_control.json b/application/src/main/data/json/system/widget_types/basic_gpio_control.json index df54377dc5..2471997b46 100644 --- a/application/src/main/data/json/system/widget_types/basic_gpio_control.json +++ b/application/src/main/data/json/system/widget_types/basic_gpio_control.json @@ -9,8 +9,8 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section.flex-1 {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n\n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", diff --git a/application/src/main/data/json/system/widget_types/basic_gpio_panel.json b/application/src/main/data/json/system/widget_types/basic_gpio_panel.json index 5a46e216a1..568b6efe75 100644 --- a/application/src/main/data/json/system/widget_types/basic_gpio_panel.json +++ b/application/src/main/data/json/system/widget_types/basic_gpio_panel.json @@ -9,8 +9,8 @@ "sizeX": 5, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section.flex-1 {\n min-width: 0px;\n}\n\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n\n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", diff --git a/application/src/main/data/json/system/widget_types/device_admin_table.json b/application/src/main/data/json/system/widget_types/device_admin_table.json index 014734df54..96fd7b8919 100644 --- a/application/src/main/data/json/system/widget_types/device_admin_table.json +++ b/application/src/main/data/json/system/widget_types/device_admin_table.json @@ -18,7 +18,7 @@ "dataKeySettingsDirective": "tb-entities-table-key-settings", "hasBasicMode": true, "basicModeDirective": "tb-entities-table-basic-config", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"entitiesTitle\":\"Device admin table\",\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"useRowStyleFunction\":false},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.8332260553092266,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.08583217494773887,\"funcBody\":\"return 'Device';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"useShowWidgetActionFunction\":null,\"showWidgetActionFunction\":\"return true;\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n if (vm.editDeviceFormGroup.get('deviceType').value !== vm.device.type) {\\n delete vm.device.deviceProfileId;\\n }\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"openInSeparateDialog\":false,\"openInPopover\":false,\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]},\"configMode\":\"basic\"}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"entitiesTitle\":\"Device admin table\",\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"useRowStyleFunction\":false},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.8332260553092266,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.08583217494773887,\"funcBody\":\"return 'Device';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"useShowWidgetActionFunction\":null,\"showWidgetActionFunction\":\"return true;\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n if (vm.editDeviceFormGroup.get('deviceType').value !== vm.device.type) {\\n delete vm.device.deviceProfileId;\\n }\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"openInSeparateDialog\":false,\"openInPopover\":false,\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]},\"configMode\":\"basic\"}" }, "tags": [ "provisioning", diff --git a/application/src/main/data/json/system/widget_types/device_claiming_widget.json b/application/src/main/data/json/system/widget_types/device_claiming_widget.json index 4ac8654ab4..02b3d1aa53 100644 --- a/application/src/main/data/json/system/widget_types/device_claiming_widget.json +++ b/application/src/main/data/json/system/widget_types/device_claiming_widget.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 4.5, "resources": [], - "templateHtml": "
\n
\n \n {{deviceLabel}}\n \n \n {{requiredErrorDevice}}\n \n \n \n {{secretKeyLabel}}\n \n \n {{requiredErrorSecretKey}}\n \n \n
\n
\n \n
\n
\n", + "templateHtml": "
\n
\n \n {{deviceLabel}}\n \n \n {{requiredErrorDevice}}\n \n \n \n {{secretKeyLabel}}\n \n \n {{requiredErrorSecretKey}}\n \n \n
\n
\n \n
\n
\n", "templateCss": ".claim-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n", "controllerScript": "let $scope;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n}\n\nfunction init() {\n $scope = self.ctx.$scope;\n let $injector = $scope.$injector;\n let utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n let $translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n let deviceService = $scope.$injector.get(self.ctx.servicesMap.get('deviceService'));\n let settings = self.ctx.settings || {};\n \n $scope.toastTargetId = 'device-claiming-widget' + utils.guid();\n $scope.secretKeyField = settings.deviceSecret;\n $scope.showLabel = settings.showLabel;\n\n let titleTemplate = \"\";\n let successfulClaim = utils.customTranslation(settings.successfulClaimDevice, settings.successfulClaimDevice) || $translate.instant('widgets.input-widgets.claim-successful');\n let failedClaimDevice = utils.customTranslation(settings.failedClaimDevice, settings.failedClaimDevice) || $translate.instant('widgets.input-widgets.claim-failed');\n let deviceNotFound = utils.customTranslation(settings.deviceNotFound, settings.deviceNotFound) || $translate.instant('widgets.input-widgets.claim-not-found');\n \n if (settings.widgetTitle && settings.widgetTitle.length) {\n titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n titleTemplate = self.ctx.widgetConfig.title;\n }\n self.ctx.widgetTitle = titleTemplate;\n \n $scope.deviceLabel = utils.customTranslation(settings.deviceLabel, settings.deviceLabel) || $translate.instant('widgets.input-widgets.device-name');\n $scope.requiredErrorDevice= utils.customTranslation(settings.requiredErrorDevice, settings.requiredErrorDevice) || $translate.instant('widgets.input-widgets.device-name-required');\n \n $scope.secretKeyLabel = utils.customTranslation(settings.secretKeyLabel, settings.secretKeyLabel) || $translate.instant('widgets.input-widgets.secret-key');\n $scope.requiredErrorSecretKey= utils.customTranslation(settings.requiredErrorSecretKey, settings.requiredErrorSecretKey) || $translate.instant('widgets.input-widgets.secret-key-required');\n \n $scope.labelClaimButon = utils.customTranslation(settings.labelClaimButon, settings.labelClaimButon) || $translate.instant('widgets.input-widgets.claim-device');\n \n $scope.claimDeviceFormGroup = $scope.fb.group(\n {deviceName: ['', [$scope.validators.required]]}\n );\n if ($scope.secretKeyField) {\n $scope.claimDeviceFormGroup.addControl('deviceSecret', $scope.fb.control('', [$scope.validators.required]));\n }\n \n $scope.claim = function(claimDeviceForm) {\n $scope.loading = true;\n\n let deviceName = $scope.claimDeviceFormGroup.get('deviceName').value;\n let claimRequest = {};\n if ($scope.secretKeyField) {\n claimRequest.secretKey = $scope.claimDeviceFormGroup.get('deviceSecret').value;\n }\n deviceService.claimDevice(deviceName, claimRequest, { ignoreErrors: true }).subscribe(\n function (data) {\n successClaim(claimDeviceForm);\n self.ctx.detectChanges();\n },\n function (error) {\n $scope.loading = false;\n if(error.status == 404) {\n $scope.showErrorToast(deviceNotFound, 'bottom', 'left', $scope.toastTargetId);\n } else {\n let errorMessage = failedClaimDevice;\n if (error.status !== 400) {\n if (error.error && error.error.message) {\n errorMessage = error.error.message;\n }\n }\n $scope.showErrorToast(errorMessage, 'bottom', 'left', $scope.toastTargetId);\n } \n self.ctx.detectChanges();\n }\n );\n }\n\n function successClaim(claimDeviceForm) {\n let deviceObj = {\n deviceName: ''\n };\n if ($scope.secretKeyField) {\n deviceObj.deviceSecret = '';\n } \n claimDeviceForm.resetForm(); \n $scope.claimDeviceFormGroup.reset(deviceObj);\n $scope.loading = false;\n $scope.showSuccessToast(successfulClaim, 2000, 'bottom', 'left', $scope.toastTargetId);\n self.ctx.updateAliases();\n }\n \n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_control.json b/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_control.json index b2e03f19a7..f2b95881d5 100644 --- a/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_control.json +++ b/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_control.json @@ -9,8 +9,8 @@ "sizeX": 6, "sizeY": 10.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section.flex-1 {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n\n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", diff --git a/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_panel.json b/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_panel.json index 1bbe2482f2..6595ec473b 100644 --- a/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_panel.json +++ b/application/src/main/data/json/system/widget_types/raspberry_pi_gpio_panel.json @@ -9,8 +9,8 @@ "sizeX": 7, "sizeY": 10.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section.flex-1 {\n min-width: 0px;\n}\n\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n\n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", diff --git a/application/src/main/data/json/system/widget_types/rpc_button.json b/application/src/main/data/json/system/widget_types/rpc_button.json index a5edbcc5a3..cd9b5d567a 100644 --- a/application/src/main/data/json/system/widget_types/rpc_button.json +++ b/application/src/main/data/json/system/widget_types/rpc_button.json @@ -9,7 +9,7 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-mdc-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", "controllerScript": "var requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n \n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n if (rpcParams.length) {\n try {\n rpcParams = JSON.parse(rpcParams);\n } catch (e) {}\n }\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n\nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/service_rpc.json b/application/src/main/data/json/system/widget_types/service_rpc.json index e53dd1118a..c21814f312 100644 --- a/application/src/main/data/json/system/widget_types/service_rpc.json +++ b/application/src/main/data/json/system/widget_types/service_rpc.json @@ -18,7 +18,7 @@ } ], "templateHtml": "", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section.flex-1 {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n\n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "\nself.onInit = function() {\n};", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", diff --git a/application/src/main/data/json/system/widget_types/update_boolean_timeseries.json b/application/src/main/data/json/system/widget_types/update_boolean_timeseries.json index 7054be76be..6467ba1836 100644 --- a/application/src/main/data/json/system/widget_types/update_boolean_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_boolean_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}", "controllerScript": "let settings;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [{\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }]\n );\n\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_device_attribute.json b/application/src/main/data/json/system/widget_types/update_device_attribute.json index 3cb54b9d84..1e7b356ca6 100644 --- a/application/src/main/data/json/system/widget_types/update_device_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_device_attribute.json @@ -9,7 +9,7 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-mdc-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n let attributeService = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n let entityId = {\n entityType: \"DEVICE\",\n id: self.ctx.defaultSubscription.targetDeviceId\n };\n attributeService.saveEntityAttributes(entityId,\n entityAttributeType, attributes).subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n\n );\n };\n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_double_timeseries.json b/application/src/main/data/json/system/widget_types/update_double_timeseries.json index 38b5a8e501..01357bdddd 100644 --- a/application/src/main/data/json/system/widget_types/update_double_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_double_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_integer_timeseries.json b/application/src/main/data/json/system/widget_types/update_integer_timeseries.json index 52039b96e9..c2d352069d 100644 --- a/application/src/main/data/json/system/widget_types/update_integer_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_integer_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}\n", "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_location_timeseries.json b/application/src/main/data/json/system/widget_types/update_location_timeseries.json index f868683a6f..33d4009415 100644 --- a/application/src/main/data/json/system/widget_types/update_location_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_location_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n \n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n\n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form mat-form-field{\n width: 100%;\n padding-right: 5px;\n}\n\n.attribute-update-form.small-width mat-form-field{\n width: 150px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\n\r\nfunction init() {\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n \r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.showGetLocation = utils.defaultValue(settings.showGetLocation, true);\r\n settings.enableHighAccuracy = utils.defaultValue(settings.enableHighAccuracy, false);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false; \r\n\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row');\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || translate.instant('widgets.input-widgets.longitude');\r\n\r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentLat: [undefined, [$scope.validators.required,\r\n $scope.validators.min(-90),\r\n $scope.validators.max(90)]],\r\n currentLng: [undefined, [$scope.validators.required,\r\n $scope.validators.min(-180),\r\n $scope.validators.max(180)]]}\r\n );\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != \"timeseries\"){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n $scope.isFocused = false;\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityTimeseries(\r\n datasource.entity.id,\r\n 'scope',\r\n [\r\n {\r\n key: settings.latKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLat').value\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLng').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalLat = $scope.attributeUpdateFormGroup.get('currentLat').value;\r\n $scope.originalLng = $scope.attributeUpdateFormGroup.get('currentLng').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n };\r\n \r\n $scope.discardChange = function() {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n 'currentLat': $scope.originalLat,\r\n 'currentLng': $scope.originalLng\r\n });\r\n $scope.isFocused = false;\r\n $scope.attributeUpdateFormGroup.markAsPristine();\r\n self.onDataUpdated();\r\n };\r\n \r\n $scope.disableButton = function () {\r\n return $scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null;\r\n };\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.blocked-location'), \r\n 'bottom', 'left', $scope.toastTargetId);\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.no-support-geolocation'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n };\r\n \r\n function showPosition(position) {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n currentLat: correctValue(position.coords.latitude),\r\n currentLng: correctValue(position.coords.longitude)\r\n });\r\n $scope.attributeUpdateFormGroup.markAsDirty();\r\n $scope.isFocused = true;\r\n }\r\n \r\n self.onResize();\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName && $scope.attributeUpdateFormGroup.get('currentLat').pristine){\r\n $scope.originalLat = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLat').patchValue(correctValue($scope.originalLat));\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName && $scope.attributeUpdateFormGroup.get('currentLng').pristine){\r\n $scope.originalLng = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLng').patchValue(correctValue($scope.originalLng));\r\n }\r\n }\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n return 0;\r\n }\r\n return value;\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container && self.ctx.$container[0].offsetWidth < 320);\r\n $scope.changeAlignment = ($scope.isHorizontal && self.ctx.$container && self.ctx.$container[0].offsetWidth < 480);\r\n self.ctx.detectChanges();\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n\r\n};", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_boolean_attribute.json b/application/src/main/data/json/system/widget_types/update_server_boolean_attribute.json index cf5d300751..7f914f8443 100644 --- a/application/src/main/data/json/system/widget_types/update_server_boolean_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_boolean_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", + "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_date_attribute.json b/application/src/main/data/json/system/widget_types/update_server_date_attribute.json index 3a1cdabc86..d460cc235c 100644 --- a/application/src/main/data/json/system/widget_types/update_server_date_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_date_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ labelValue }}\n \n \n \n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ labelValue }}\n \n \n \n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\nfunction init() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.entityDetected = false;\r\n\r\n $scope.datePickerType = settings.showTimeInput ? 'datetime' : 'date'; \r\n \r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\r\n $scope.labelValue = translate.instant('widgets.input-widgets.date');\r\n \r\n if (settings.showTimeInput) {\r\n $scope.labelValue += \" & \" + translate.instant('widgets.input-widgets.time');\r\n }\r\n \r\n var validators = [];\r\n \r\n if (settings.isRequired) {\r\n validators.push($scope.validators.required);\r\n }\r\n \r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentValue: [undefined, validators]}\r\n );\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n \r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type !== \"attribute\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n \r\n $scope.clear = function(event) {\r\n event.stopPropagation();\r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(undefined);\r\n }\r\n \r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n var currentValueInMilliseconds;\r\n \r\n if (!$scope.attributeUpdateFormGroup.get('currentValue').value) {\r\n currentValueInMilliseconds = undefined;\r\n } else {\r\n currentValueInMilliseconds = $scope.attributeUpdateFormGroup.get('currentValue').value.getTime();\r\n }\r\n \r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SERVER_SCOPE',\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: currentValueInMilliseconds\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n \r\n $scope.isValidDate = function(date) {\r\n return date instanceof Date && !isNaN(date);\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.originalValue = moment(self.ctx.data[0].data[0][1]).toDate();\r\n\r\n if (!$scope.isValidDate($scope.originalValue)) {\r\n $scope.originalValue = undefined;\r\n }\r\n \r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nself.onResize = function() {\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n}\r\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_double_attribute.json b/application/src/main/data/json/system/widget_types/update_server_double_attribute.json index ddfe7b36d1..b7dab2b2a6 100644 --- a/application/src/main/data/json/system/widget_types/update_server_double_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_double_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [$scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_image_attribute.json b/application/src/main/data/json/system/widget_types/update_server_image_attribute.json index 79af76660c..b2b3c9600f 100644 --- a/application/src/main/data/json/system/widget_types/update_server_image_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_image_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n \n
\n \n
\n \n \n
\n
\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n \n
\n\n
\n \n \n
\n
\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.tb-image-preview-container div,\n.tb-flow-drop label {\n font-size: 16px !important;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\nfunction init() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.displayPreview = utils.defaultValue(settings.displayPreview, true);\r\n settings.displayClearButton = utils.defaultValue(settings.displayClearButton, false);\r\n settings.displayApplyButton = utils.defaultValue(settings.displayApplyButton, true);\r\n settings.displayDiscardButton = utils.defaultValue(settings.displayDiscardButton, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.entityDetected = false;\r\n \r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentValue: [undefined, []]}\r\n );\r\n \r\n $scope.attributeUpdateFormGroup.valueChanges.subscribe( () => {\r\n self.ctx.detectChanges();\r\n if (!settings.displayApplyButton) {\r\n $scope.updateAttribute();\r\n }\r\n });\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n \r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type !== \"attribute\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n \r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SERVER_SCOPE',\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n if (settings.displayApplyButton) {\r\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\r\n }\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n var value = self.ctx.data[0].data[0][1];\r\n if (settings.displayApplyButton || !$scope.originalValue) {\r\n $scope.originalValue = value;\r\n }\r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(value, {emitEvent: false});\r\n self.ctx.detectChanges();\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nself.onResize = function() {\r\n\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n $scope.attributeUpdateFormGroup.valueChanges.unsubscribe();\r\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_integer_attribute.json b/application/src/main/data/json/system/widget_types/update_server_integer_attribute.json index ea4fecdb42..066d3487ef 100644 --- a/application/src/main/data/json/system/widget_types/update_server_integer_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_integer_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)\n ];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n \n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_location_attribute.json b/application/src/main/data/json/system/widget_types/update_server_location_attribute.json index 0d007ca955..281de28846 100644 --- a/application/src/main/data/json/system/widget_types/update_server_location_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_location_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n \n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n\n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form mat-form-field{\n width: 100%;\n padding-right: 5px;\n}\n\n.attribute-update-form.small-width mat-form-field{\n width: 150px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\nfunction init() {\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n \r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.showGetLocation = utils.defaultValue(settings.showGetLocation, true);\r\n settings.enableHighAccuracy = utils.defaultValue(settings.enableHighAccuracy, false);\r\n settings.isLatRequired = utils.defaultValue(settings.isLatRequired, true);\r\n settings.isLngRequired = utils.defaultValue(settings.isLngRequired, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false; \r\n\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row');\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || translate.instant('widgets.input-widgets.longitude');\r\n \r\n var validatorsLat = [$scope.validators.min(-90), $scope.validators.max(90)];\r\n var validatorsLng = [$scope.validators.min(-180), $scope.validators.max(180)];\r\n \r\n if (settings.isLatRequired) {\r\n validatorsLat.push($scope.validators.required);\r\n }\r\n \r\n if (settings.isLngRequired) {\r\n validatorsLng.push($scope.validators.required);\r\n }\r\n\r\n $scope.attributeUpdateFormGroup = $scope.fb.group({\r\n currentLat: [undefined, validatorsLat],\r\n currentLng: [undefined, validatorsLng]\r\n });\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != \"attribute\") {\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n $scope.isFocused = false;\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SERVER_SCOPE',\r\n [\r\n {\r\n key: settings.latKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLat').value\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLng').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalLat = $scope.attributeUpdateFormGroup.get('currentLat').value;\r\n $scope.originalLng = $scope.attributeUpdateFormGroup.get('currentLng').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n };\r\n \r\n $scope.discardChange = function() {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n 'currentLat': $scope.originalLat,\r\n 'currentLng': $scope.originalLng\r\n });\r\n $scope.isFocused = false;\r\n $scope.attributeUpdateFormGroup.markAsPristine();\r\n self.onDataUpdated();\r\n };\r\n \r\n $scope.disableButton = function () {\r\n return $scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null;\r\n };\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.blocked-location'), \r\n 'bottom', 'left', $scope.toastTargetId);\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.no-support-geolocation'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n };\r\n \r\n function showPosition(position) {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n currentLat: correctValue(position.coords.latitude),\r\n currentLng: correctValue(position.coords.longitude)\r\n });\r\n $scope.attributeUpdateFormGroup.markAsDirty();\r\n $scope.isFocused = true;\r\n }\r\n \r\n self.onResize();\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName && $scope.attributeUpdateFormGroup.get('currentLat').pristine){\r\n $scope.originalLat = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLat').patchValue(correctValue($scope.originalLat));\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName && $scope.attributeUpdateFormGroup.get('currentLng').pristine){\r\n $scope.originalLng = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLng').patchValue(correctValue($scope.originalLng));\r\n }\r\n }\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n return 0;\r\n }\r\n return value;\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container && self.ctx.$container[0].offsetWidth < 320);\r\n $scope.changeAlignment = ($scope.isHorizontal && self.ctx.$container && self.ctx.$container[0].offsetWidth < 480);\r\n self.ctx.detectChanges();\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n\r\n};", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json index d8c33bffcc..ed4f8038e1 100644 --- a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n \n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_boolean_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_boolean_attribute.json index 765c6381f2..232d09195f 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_boolean_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_boolean_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", + "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}", "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue || false\n }\n ]\n ).subscribe(\n function success() {\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_date_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_date_attribute.json index 2225111ff7..8d775ba24a 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_date_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_date_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ labelValue }}\n \n \n \n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ labelValue }}\n \n \n \n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\nfunction init() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.entityDetected = false;\r\n\r\n $scope.datePickerType = settings.showTimeInput ? 'datetime' : 'date'; \r\n \r\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\r\n $scope.labelValue = translate.instant('widgets.input-widgets.date');\r\n \r\n \r\n if (settings.showTimeInput) {\r\n $scope.labelValue += \" & \" + translate.instant('widgets.input-widgets.time');\r\n }\r\n \r\n var validators = [];\r\n \r\n if (settings.isRequired) {\r\n validators.push($scope.validators.required);\r\n }\r\n \r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentValue: [undefined, validators]}\r\n );\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n \r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType === 'DEVICE') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n \r\n $scope.entityDetected = true;\r\n }\r\n } else {\r\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type !== \"attribute\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n \r\n $scope.clear = function(event) {\r\n event.stopPropagation();\r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(undefined);\r\n }\r\n \r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n var currentValueInMilliseconds;\r\n \r\n if (!$scope.attributeUpdateFormGroup.get('currentValue').value) {\r\n currentValueInMilliseconds = undefined;\r\n } else {\r\n currentValueInMilliseconds = $scope.attributeUpdateFormGroup.get('currentValue').value.getTime();\r\n }\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SHARED_SCOPE',\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: currentValueInMilliseconds\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n \r\n $scope.isValidDate = function(date) {\r\n return date instanceof Date && !isNaN(date);\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.originalValue = moment(self.ctx.data[0].data[0][1]).toDate();\r\n \r\n if (!$scope.isValidDate($scope.originalValue)) {\r\n $scope.originalValue = undefined;\r\n }\r\n \r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nself.onResize = function() {\r\n\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_double_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_double_attribute.json index 29c96f71b4..e2cd304a54 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_double_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_double_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)\n ];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n \n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_image_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_image_attribute.json index 895ef845c9..1feedab987 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_image_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_image_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n \n
\n\n
\n \n \n
\n
\n\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.tb-image-preview-container div,\n.tb-flow-drop label {\n font-size: 16px !important;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\nfunction init() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.displayPreview = utils.defaultValue(settings.displayPreview, true);\r\n settings.displayClearButton = utils.defaultValue(settings.displayClearButton, false);\r\n settings.displayApplyButton = utils.defaultValue(settings.displayApplyButton, true);\r\n settings.displayDiscardButton = utils.defaultValue(settings.displayDiscardButton, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.entityDetected = false;\r\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\r\n \r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentValue: [undefined, []]}\r\n );\r\n \r\n $scope.attributeUpdateFormGroup.valueChanges.subscribe( () => {\r\n self.ctx.detectChanges();\r\n if (!settings.displayApplyButton) {\r\n $scope.updateAttribute();\r\n }\r\n });\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n \r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType === 'DEVICE') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n \r\n $scope.entityDetected = true;\r\n }\r\n } else {\r\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type !== \"attribute\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n \r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SHARED_SCOPE',\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n if (settings.displayApplyButton) {\r\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\r\n }\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n var value = self.ctx.data[0].data[0][1];\r\n if (settings.displayApplyButton || !$scope.originalValue) {\r\n $scope.originalValue = value;\r\n }\r\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(value, {emitEvent: false});\r\n self.ctx.detectChanges();\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nself.onResize = function() {\r\n\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n $scope.attributeUpdateFormGroup.valueChanges.unsubscribe();\r\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_integer_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_integer_attribute.json index c4dd5b2dba..4235127d7a 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_integer_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_integer_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)\n ];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_location_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_location_attribute.json index fa053fed72..6f2067dec0 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_location_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_location_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n \n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n\n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n \n
\n
\n\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form mat-form-field{\n width: 100%;\n padding-right: 5px;\n}\n\n.attribute-update-form.small-width mat-form-field{\n width: 150px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\n\r\nfunction init() {\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n \r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.showGetLocation = utils.defaultValue(settings.showGetLocation, true);\r\n settings.enableHighAccuracy = utils.defaultValue(settings.enableHighAccuracy, false);\r\n settings.isLatRequired = utils.defaultValue(settings.isLatRequired, true);\r\n settings.isLngRequired = utils.defaultValue(settings.isLngRequired, true);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false; \r\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\r\n\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row');\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || translate.instant('widgets.input-widgets.longitude');\r\n\r\n var validatorsLat = [$scope.validators.min(-90), $scope.validators.max(90)];\r\n var validatorsLng = [$scope.validators.min(-180), $scope.validators.max(180)];\r\n \r\n if (settings.isLatRequired) {\r\n validatorsLat.push($scope.validators.required);\r\n }\r\n \r\n if (settings.isLngRequired) {\r\n validatorsLng.push($scope.validators.required);\r\n }\r\n\r\n $scope.attributeUpdateFormGroup = $scope.fb.group({\r\n currentLat: [undefined, validatorsLat],\r\n currentLng: [undefined, validatorsLng],\r\n });\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType === 'DEVICE') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n \r\n $scope.entityDetected = true;\r\n }\r\n } else {\r\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != \"attribute\"){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n $scope.isFocused = false;\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entity.id,\r\n 'SHARED_SCOPE',\r\n [\r\n {\r\n key: settings.latKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLat').value\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLng').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalLat = $scope.attributeUpdateFormGroup.get('currentLat').value;\r\n $scope.originalLng = $scope.attributeUpdateFormGroup.get('currentLng').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n };\r\n \r\n $scope.discardChange = function() {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n 'currentLat': $scope.originalLat,\r\n 'currentLng': $scope.originalLng\r\n });\r\n $scope.isFocused = false;\r\n $scope.attributeUpdateFormGroup.markAsPristine();\r\n self.onDataUpdated();\r\n };\r\n \r\n $scope.disableButton = function () {\r\n return $scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null;\r\n };\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.blocked-location'), \r\n 'bottom', 'left', $scope.toastTargetId);\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.no-support-geolocation'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n };\r\n \r\n function showPosition(position) {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n currentLat: correctValue(position.coords.latitude),\r\n currentLng: correctValue(position.coords.longitude)\r\n });\r\n $scope.attributeUpdateFormGroup.markAsDirty();\r\n $scope.isFocused = true;\r\n }\r\n \r\n self.onResize();\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName && $scope.attributeUpdateFormGroup.get('currentLat').pristine){\r\n $scope.originalLat = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLat').patchValue(correctValue($scope.originalLat));\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName && $scope.attributeUpdateFormGroup.get('currentLng').pristine){\r\n $scope.originalLng = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLng').patchValue(correctValue($scope.originalLng));\r\n }\r\n }\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n return 0;\r\n }\r\n return value;\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container && self.ctx.$container[0].offsetWidth < 320);\r\n $scope.changeAlignment = ($scope.isHorizontal && self.ctx.$container && self.ctx.$container[0].offsetWidth < 480);\r\n self.ctx.detectChanges();\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n\r\n};", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json index 7578838066..e9a00672bd 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/update_string_timeseries.json b/application/src/main/data/json/system/widget_types/update_string_timeseries.json index d54224ef36..e6fc91f316 100644 --- a/application/src/main/data/json/system/widget_types/update_string_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_string_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, validators]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 668b92b7b2..1f32024d7f 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -186,7 +186,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "customPretty", - "customHtml": "
\n \n

{{ 'gateway.launch-command' | translate }}

\n \n
\n \n
\n \n
\n \n
\n
\n", + "customHtml": "
\n \n

{{ 'gateway.launch-command' | translate }}

\n \n
\n \n
\n \n
\n \n
\n
\n", "customCss": ".container {\n display: grid;\n grid-template-rows: min-content minmax(auto, 1fr) min-content;\n height: 100%;\n max-height: 100vh;\n width: 600px;\n max-width: 100%;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenCommands();\n\nfunction openCommands() {\n customDialog.customDialog(htmlTemplate, CommandsDialogController, {panelClass: \"test\"}).subscribe();\n}\n\nfunction CommandsDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n", "customResources": [], @@ -246,7 +246,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "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", + "customHtml": "
\n \n

Add gateway

\n \n \n
\n \n \n
\n
\n
\n \n Name\n \n \n Gateway name is required.\n \n \n
\n
\n \n
\n
\n
\n \n \n
\n
\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 == \"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": [], @@ -592,7 +592,7 @@ "settings": { "useMarkdownTextFunction": true, "markdownTextPattern": "# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.", - "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n
\n \n ${generateMatHeader(index)}\n ${label}\n
\n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Enabled | Disabled)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `
${blockData}
`;", + "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n
\n \n ${generateMatHeader(index)}\n ${label}\n
\n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Enabled | Disabled)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `
${blockData}
`;", "applyDefaultMarkdownStyle": false, "markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}" }, @@ -1916,7 +1916,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "customPretty", - "customHtml": "
\n \n

{{ 'gateway.launch-command' | translate }}

\n \n
\n \n
\n \n
\n \n
\n
\n", + "customHtml": "
\n \n

{{ 'gateway.launch-command' | translate }}

\n \n
\n \n
\n \n
\n \n
\n
\n", "customCss": ".container {\n display: grid;\n grid-template-rows: min-content minmax(auto, 1fr) min-content;\n height: 100%;\n max-height: 100vh;\n width: 600px;\n max-width: 100%;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenCommands();\n\nfunction openCommands() {\n customDialog.customDialog(htmlTemplate, CommandsDialogController, {panelClass: \"test\"}).subscribe();\n}\n\nfunction CommandsDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n", "customResources": [], @@ -1998,7 +1998,7 @@ "padding": "8px", "settings": { "useMarkdownTextFunction": true, - "markdownTextFunction": "let buttonsHtml = \"\" \nctx.actionsApi.getActionDescriptors('elementClick').forEach((btn, index)=>{\n let disabled =false;\n if (index == 2) {\n disabled = data[0] && data[0].RemoteLoggingLevel ? data[0].RemoteLoggingLevel == \"NONE\" : true;\n } else if (index == 4) {\n const conf = data[0].general_configuration? JSON.parse(data[0].general_configuration): {};\n disabled = !conf.remoteShell;\n }\n buttonsHtml += ``\n});\n\nreturn `
${buttonsHtml}
`;", + "markdownTextFunction": "let buttonsHtml = \"\" \nctx.actionsApi.getActionDescriptors('elementClick').forEach((btn, index)=>{\n let disabled =false;\n if (index == 2) {\n disabled = data[0] && data[0].RemoteLoggingLevel ? data[0].RemoteLoggingLevel == \"NONE\" : true;\n } else if (index == 4) {\n const conf = data[0].general_configuration? JSON.parse(data[0].general_configuration): {};\n disabled = !conf.remoteShell;\n }\n buttonsHtml += ``\n});\n\nreturn `
${buttonsHtml}
`;", "applyDefaultMarkdownStyle": false, "markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: start;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}" }, @@ -2605,7 +2605,7 @@ "padding": "0px", "settings": { "useMarkdownTextFunction": false, - "markdownTextPattern": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n", + "markdownTextPattern": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n", "applyDefaultMarkdownStyle": false, "markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}\n\n.devices-tabs {\n height: 100%;\n}\n\n::ng-deep .mat-mdc-tab-body-wrapper {\n height: 100%;\n}" }, diff --git a/application/src/main/data/upgrade/3.8.1/schema_update.sql b/application/src/main/data/upgrade/3.8.1/schema_update.sql index 6b87dc6dde..1084dd374f 100644 --- a/application/src/main/data/upgrade/3.8.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.8.1/schema_update.sql @@ -14,3 +14,13 @@ -- limitations under the License. -- +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS last_login_ts BIGINT; +UPDATE user_credentials c SET last_login_ts = (SELECT (additional_info::json ->> 'lastLoginTs')::bigint FROM tb_user u WHERE u.id = c.user_id) + WHERE last_login_ts IS NULL; + +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS failed_login_attempts INT; +UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::json ->> 'failedLoginAttempts')::int FROM tb_user u WHERE u.id = c.user_id) + WHERE failed_login_attempts IS NULL; + +UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts' - 'userCredentialsEnabled')::text + WHERE additional_info IS NOT NULL AND additional_info != 'null'; diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index a2115360d8..d65cc58d87 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -235,7 +235,15 @@ public class AdminController extends BaseController { } } String email = getCurrentUser().getEmail(); - mailService.sendTestMail(adminSettings.getJsonValue(), email); + try { + mailService.sendTestMail(adminSettings.getJsonValue(), email); + } catch (ThingsboardException e) { + String error = e.getMessage(); + if (e.getCause() != null) { + error += ": " + e.getCause().getMessage(); // showing actual underlying error for testing purposes + } + throw new ThingsboardException(error, e.getErrorCode()); + } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index fdc068de9e..600980316a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -217,7 +217,7 @@ public class AuthController extends BaseController { try { mailService.sendAccountActivatedEmail(loginUrl, email); } catch (Exception e) { - log.info("Unable to send account activation email [{}]", e.getMessage()); + log.warn("Unable to send account activation email [{}]", e.getMessage()); } } @@ -256,7 +256,11 @@ public class AuthController extends BaseController { String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); - mailService.sendPasswordWasResetEmail(loginUrl, email); + try { + mailService.sendPasswordWasResetEmail(loginUrl, email); + } catch (Exception e) { + log.warn("Couldn't send password was reset email: {}", e.getMessage()); + } eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 9b7616d3fa..da7d6243fc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; @@ -402,7 +403,7 @@ public abstract class BaseController { || exception instanceof DataValidationException || cause instanceof IncorrectParameterException) { return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); } else if (exception instanceof MessagingException) { - return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); + return new ThingsboardException("Unable to send mail", ThingsboardErrorCode.GENERAL); } else if (exception instanceof AsyncRequestTimeoutException) { return new ThingsboardException("Request timeout", ThingsboardErrorCode.GENERAL); } else if (exception instanceof DataAccessException) { @@ -877,14 +878,18 @@ public abstract class BaseController { } protected void checkUserInfo(User user) throws ThingsboardException { + ObjectNode info; if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) { - checkDashboardInfo(additionalInfo); - - UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); - } + info = additionalInfo; + checkDashboardInfo(info); + } else { + info = JacksonUtil.newObjectNode(); + user.setAdditionalInfo(info); } + + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); + info.put("userCredentialsEnabled", userCredentials.isEnabled()); + info.put("lastLoginTs", userCredentials.getLastLoginTs()); } protected void checkDashboardInfo(JsonNode additionalInfo) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 574a9dfc2e..b953fbe8d2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -78,7 +78,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.security.system.SystemSecurityService; import java.util.ArrayList; import java.util.Arrays; @@ -123,7 +122,6 @@ public class UserController extends BaseController { private final MailService mailService; private final JwtTokenFactory tokenFactory; - private final SystemSecurityService systemSecurityService; private final ApplicationEventPublisher eventPublisher; private final TbUserService tbUserService; private final EntityQueryService entityQueryService; @@ -219,7 +217,11 @@ public class UserController extends BaseController { accessControlService.checkPermission(securityUser, Resource.USER, Operation.READ, user.getId(), user); UserActivationLink activationLink = tbUserService.getActivationLink(securityUser.getTenantId(), securityUser.getCustomerId(), user.getId(), request); - mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), email); + try { + mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), email); + } catch (Exception e) { + throw new ThingsboardException("Couldn't send user activation email", ThingsboardErrorCode.GENERAL); + } } @ApiOperation(value = "Get activation link (getActivationLink)", diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 3407f26ba6..4ae8d117be 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -171,6 +171,7 @@ public class ThingsboardInstallService { log.info("Installing DataBase schema for entities..."); entityDatabaseSchemaService.createDatabaseSchema(); + entityDatabaseSchemaService.createSchemaVersion(); entityDatabaseSchemaService.createOrUpdateViewsAndFunctions(); entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 17ceb17ed1..893bd13171 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -50,7 +50,6 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.user.UserServiceImpl; /** * This event listener does not support async event processing because relay on ThreadLocal @@ -226,13 +225,10 @@ public class EdgeEventSourcingListener { } private void cleanUpUserAdditionalInfo(User user) { - // reset FAILED_LOGIN_ATTEMPTS and LAST_LOGIN_TS - edge is not interested in this information if (user.getAdditionalInfo() instanceof NullNode) { user.setAdditionalInfo(null); } if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) { - additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS); - additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS); if (additionalInfo.isEmpty()) { user.setAdditionalInfo(null); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java index 42c692f82f..8cdc30216a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java @@ -74,7 +74,7 @@ public class DashboardSyncService { List resources = listFiles("resources"); for (RepoFile resourceFile : resources) { String data = getFileContent(resourceFile.path()); - resourceService.updateSystemResource(ResourceType.JS_MODULE, resourceFile.name(), data); + resourceService.createOrUpdateSystemResource(ResourceType.JS_MODULE, resourceFile.name(), data); } Stream widgetsBundles = listFiles("widget_bundles").stream() @@ -85,7 +85,7 @@ public class DashboardSyncService { RepoFile dashboardFile = listFiles("dashboards").get(0); String dashboardJson = getFileContent(dashboardFile.path()); - resourceService.updateSystemResource(ResourceType.DASHBOARD, GATEWAYS_DASHBOARD_KEY, dashboardJson); + resourceService.createOrUpdateSystemResource(ResourceType.DASHBOARD, GATEWAYS_DASHBOARD_KEY, dashboardJson); log.info("Gateways dashboard sync completed"); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index ad4b7c097e..4e9e8e954c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -60,7 +60,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), savedUser.getEmail()); } catch (ThingsboardException e) { userService.deleteUser(tenantId, savedUser); - throw e; + throw new ThingsboardException("Couldn't send user activation email", ThingsboardErrorCode.GENERAL); } } logEntityActionService.logEntityAction(tenantId, savedUser.getId(), savedUser, customerId, actionType, user); diff --git a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java index 68f19e7a02..02241367a1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java @@ -23,4 +23,6 @@ public interface EntityDatabaseSchemaService extends DatabaseSchemaService { void createCustomerTitleUniqueConstraintIfNotExists(); + void createSchemaVersion(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index fd5c8a8294..ba0972a1e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplat import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; @@ -58,7 +59,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; @@ -210,14 +213,91 @@ public class InstallScripts { public void loadSystemWidgets() throws Exception { log.info("Loading system widgets"); - + Map widgetsBundlesMap = new HashMap<>(); Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); - Stream bundles = listDir(widgetBundlesDir).filter(path -> path.toString().endsWith(JSON_EXT)).map(this::getContent); + try (DirectoryStream dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + JsonNode widgetsBundleDescriptorJson; + try { + widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile()); + } catch (Exception e) { + log.error("Unable to parse widgets bundle from json: [{}]", path); + throw new RuntimeException("Unable to parse widgets bundle from json", e); + } + if (widgetsBundleDescriptorJson == null || !widgetsBundleDescriptorJson.has("widgetsBundle")) { + log.error("Invalid widgets bundle json: [{}]", path); + throw new RuntimeException("Invalid widgets bundle json: [" + path + "]"); + } + widgetsBundlesMap.put(path, widgetsBundleDescriptorJson); + JsonNode bundleAliasNode = widgetsBundleDescriptorJson.get("widgetsBundle").get("alias"); + if (bundleAliasNode == null || !bundleAliasNode.isTextual()) { + log.error("Invalid widgets bundle json: [{}]", path); + throw new RuntimeException("Invalid widgets bundle json: [" + path + "]"); + } + String bundleAlias = bundleAliasNode.asText(); + try { + this.deleteSystemWidgetBundle(bundleAlias); + } catch (Exception e) { + log.error("Failed to delete system widgets bundle: [{}]", bundleAlias); + throw new RuntimeException("Failed to delete system widgets bundle: [" + bundleAlias + "]", e); + } + } + ); + } Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR); - Stream widgets = listDir(widgetTypesDir).filter(path -> path.toString().endsWith(JSON_EXT)).map(this::getContent); - widgetsBundleService.updateSystemWidgets(bundles, widgets); - - loadSystemScadaSymbols(); + if (Files.exists(widgetTypesDir)) { + try (DirectoryStream dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + try { + JsonNode widgetTypeJson = JacksonUtil.toJsonNode(path.toFile()); + WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + widgetTypeService.saveWidgetType(widgetTypeDetails); + } catch (Exception e) { + log.error("Unable to load widget type from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widget type from json", e); + } + } + ); + } + } + this.loadSystemScadaSymbols(); + for (var widgetsBundleDescriptorEntry : widgetsBundlesMap.entrySet()) { + Path path = widgetsBundleDescriptorEntry.getKey(); + try { + JsonNode widgetsBundleDescriptorJson = widgetsBundleDescriptorEntry.getValue(); + JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); + WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class); + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); + List widgetTypeFqns = new ArrayList<>(); + if (widgetsBundleDescriptorJson.has("widgetTypes")) { + JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); + widgetTypesArrayJson.forEach( + widgetTypeJson -> { + try { + WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); + widgetTypeFqns.add(savedWidgetType.getFqn()); + } catch (Exception e) { + log.error("Unable to load widget type from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widget type from json", e); + } + } + ); + } + if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) { + JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns"); + widgetFqnsArrayJson.forEach(fqnJson -> { + widgetTypeFqns.add(fqnJson.asText()); + }); + } + widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns); + } catch (Exception e) { + log.error("Unable to load widgets bundle from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widgets bundle from json", e); + } + } } private void loadSystemScadaSymbols() throws Exception { @@ -308,6 +388,14 @@ public class InstallScripts { return widgetTypeService.saveWidgetType(scadaSymbolWidget); } + private void deleteSystemWidgetBundle(String bundleAlias) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias); + if (widgetsBundle != null) { + widgetTypeService.deleteWidgetTypesByBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId()); + widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId()); + } + } + public void updateImages() { imagesUpdater.updateWidgetsBundlesImages(); imagesUpdater.updateWidgetTypesImages(); @@ -435,7 +523,7 @@ public class InstallScripts { String resourceKey = resourceFile.getFileName().toString(); try { String data = getContent(resourceFile); - TbResource resource = resourceService.updateSystemResource(resourceType, resourceKey, data); + TbResource resource = resourceService.createOrUpdateSystemResource(resourceType, resourceKey, data); log.info("{} resource {}", (resource.getId() == null ? "Created" : "Updated"), resourceKey); } catch (Exception e) { throw new RuntimeException("Unable to load system resource " + resourceFile, e); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java index 5e1357a48e..3bb8b0e58f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java @@ -16,7 +16,10 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service @@ -29,6 +32,11 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.sql"; public static final String SCHEMA_VIEWS_AND_FUNCTIONS_SQL = "schema-views-and-functions.sql"; + @Autowired + private BuildProperties buildProperties; + @Autowired + private JdbcTemplate jdbcTemplate; + public SqlEntityDatabaseSchemaService() { super(SCHEMA_ENTITIES_SQL, SCHEMA_ENTITIES_IDX_SQL); } @@ -59,4 +67,26 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer "ALTER TABLE customer ADD CONSTRAINT customer_title_unq_key UNIQUE(tenant_id, title); END IF; END; $$;", "create 'customer_title_unq_key' constraint if it doesn't already exist!"); } + + @Override + public void createSchemaVersion() { + try { + Long schemaVersion = jdbcTemplate.queryForList("SELECT schema_version FROM tb_schema_settings", Long.class).stream().findFirst().orElse(null); + if (schemaVersion == null) { + jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version) VALUES (" + getSchemaVersion() + ")"); + } + } catch (Exception e) { + log.warn("Failed to create schema version [{}]!", buildProperties.getVersion(), e); + } + } + + private int getSchemaVersion() { + String[] versionParts = buildProperties.getVersion().replaceAll("[^\\d.]", "").split("\\."); + + int major = Integer.parseInt(versionParts[0]); + int minor = Integer.parseInt(versionParts[1]); + int patch = versionParts.length > 2 ? Integer.parseInt(versionParts[2]) : 0; + + return major * 1000000 + minor * 1000 + patch; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java index 66af786c83..428101ee05 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.install; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.util.SqlTsDao; @@ -25,9 +24,6 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Profile("install") public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { - @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") - private String partitionType; - public SqlTsDatabaseSchemaService() { super("schema-ts-psql.sql", null); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1603720450..d850dca43d 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -443,17 +443,16 @@ public class DefaultMailService implements MailService { } } - private void sendMailWithTimeout(JavaMailSender mailSender, MimeMessage msg, long timeout) { + private void sendMailWithTimeout(JavaMailSender mailSender, MimeMessage msg, long timeout) throws ThingsboardException { var submittedMail = Futures.withTimeout( mailExecutorService.submit(() -> mailSender.send(msg)), timeout, TimeUnit.MILLISECONDS, timeoutScheduler); try { submittedMail.get(timeout, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { - log.debug("Error during mail submission", e); throw new RuntimeException("Timeout!"); } catch (Exception e) { - throw new RuntimeException(ExceptionUtils.getRootCause(e)); + throw new ThingsboardException("Unable to send mail", ExceptionUtils.getRootCause(e), ThingsboardErrorCode.GENERAL); } } @@ -463,20 +462,20 @@ public class DefaultMailService implements MailService { Template template = freemarkerConfig.getTemplate(templateLocation); return FreeMarkerTemplateUtils.processTemplateIntoString(template, model); } catch (Exception e) { - throw handleException(e); + log.warn("Failed to process mail template: {}", ExceptionUtils.getRootCauseMessage(e)); + throw new ThingsboardException("Failed to process mail template: " + e.getMessage(), e, ThingsboardErrorCode.GENERAL); } } - protected ThingsboardException handleException(Exception exception) { - String message; + protected ThingsboardException handleException(Throwable exception) { + if (exception instanceof ThingsboardException thingsboardException) { + return thingsboardException; + } if (exception instanceof NestedRuntimeException) { - message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); - } else { - message = exception.getMessage(); + exception = ((NestedRuntimeException) exception).getMostSpecificCause(); } - log.warn("Unable to send mail: {}", message); - return new ThingsboardException(String.format("Unable to send mail: %s", message), - ThingsboardErrorCode.GENERAL); + log.warn("Unable to send mail: {}", exception.getMessage()); + return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java index 4add550090..7cdbc9d54f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java @@ -57,7 +57,11 @@ public class EmailTwoFaProvider extends OtpBasedTwoFaProvider subscription = sessionSubscriptions.remove(subscriptionId); if (subscription != null) { - if (sessionSubscriptions.isEmpty()) { subscriptionsBySessionId.remove(sessionId); } @@ -304,22 +303,26 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Override public void cancelAllSessionSubscriptions(TenantId tenantId, String sessionId) { log.debug("[{}][{}] Going to remove session subscriptions.", tenantId, sessionId); - List results = new ArrayList<>(); Lock subsLock = getSubsLock(tenantId); subsLock.lock(); try { Map> sessionSubscriptions = subscriptionsBySessionId.remove(sessionId); if (sessionSubscriptions != null) { - for (TbSubscription subscription : sessionSubscriptions.values()) { - results.add(modifySubscription(tenantId, subscription.getEntityId(), subscription, false)); - } + Map>> entitySubscriptions = + sessionSubscriptions.values().stream().collect(Collectors.groupingBy(TbSubscription::getEntityId)); + + entitySubscriptions.forEach((entityId, subscriptions) -> { + TbEntitySubEvent event = removeAllSubscriptions(tenantId, entityId, subscriptions); + if (event != null) { + pushSubscriptionsEvent(tenantId, entityId, event); + } + }); } else { log.debug("[{}][{}] No session subscriptions found!", tenantId, sessionId); } } finally { subsLock.unlock(); } - results.stream().filter(SubscriptionModificationResult::hasEvent).forEach(this::pushSubscriptionEvent); } @Override @@ -500,6 +503,30 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer return new SubscriptionModificationResult(tenantId, entityId, subscription, missedUpdatesCandidate, event); } + private TbEntitySubEvent removeAllSubscriptions(TenantId tenantId, EntityId entityId, List> subscriptions) { + TbEntitySubEvent event = null; + try { + TbEntityLocalSubsInfo entitySubs = subscriptionsByEntityId.get(entityId.getId()); + event = entitySubs.removeAll(subscriptions); + if (entitySubs.isEmpty()) { + subscriptionsByEntityId.remove(entityId.getId()); + entityUpdates.remove(entityId.getId()); + } + } catch (Exception e) { + log.warn("[{}][{}] Failed to remove all subscriptions {} due to ", tenantId, entityId, subscriptions, e); + } + return event; + } + + private void pushSubscriptionsEvent(TenantId tenantId, EntityId entityId, TbEntitySubEvent event) { + try { + log.trace("[{}][{}] Event: {}", tenantId, entityId, event); + pushSubEventToManagerService(tenantId, entityId, event); + } catch (Exception e) { + log.warn("[{}][{}] Failed to push subscription event {} due to ", tenantId, entityId, event, e); + } + } + private void pushSubscriptionEvent(SubscriptionModificationResult modificationResult) { try { TbEntitySubEvent event = modificationResult.getEvent(); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java index f5a5639ec0..ee20843538 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -129,13 +130,64 @@ public class TbEntityLocalSubsInfo { if (!subs.remove(sub)) { return null; } - if (subs.isEmpty()) { + if (isEmpty()) { return toEvent(ComponentLifecycleEvent.DELETED); } - TbSubscriptionsInfo oldState = state.copy(); - TbSubscriptionsInfo newState = new TbSubscriptionsInfo(); + TbSubscriptionType type = sub.getType(); + TbSubscriptionsInfo newState = state.copy(); + clearState(newState, type); + return updateState(Set.of(type), newState); + } + + public TbEntitySubEvent removeAll(List> subsToRemove) { + Set changedTypes = new HashSet<>(); + TbSubscriptionsInfo newState = state.copy(); + for (TbSubscription sub : subsToRemove) { + log.trace("[{}][{}][{}] Removing: {}", tenantId, entityId, sub.getSubscriptionId(), sub); + if (!subs.remove(sub)) { + continue; + } + if (isEmpty()) { + return toEvent(ComponentLifecycleEvent.DELETED); + } + TbSubscriptionType type = sub.getType(); + if (changedTypes.contains(type)) { + continue; + } + + clearState(newState, type); + changedTypes.add(type); + } + + return updateState(changedTypes, newState); + } + + private void clearState(TbSubscriptionsInfo state, TbSubscriptionType type) { + switch (type) { + case NOTIFICATIONS: + case NOTIFICATIONS_COUNT: + state.notifications = false; + break; + case ALARMS: + state.alarms = false; + break; + case ATTRIBUTES: + state.attrAllKeys = false; + state.attrKeys = null; + break; + case TIMESERIES: + state.tsAllKeys = false; + state.tsKeys = null; + } + } + + private TbEntitySubEvent updateState(Set updatedTypes, TbSubscriptionsInfo newState) { for (TbSubscription subscription : subs) { - switch (subscription.getType()) { + TbSubscriptionType type = subscription.getType(); + if (!updatedTypes.contains(type)) { + continue; + } + switch (type) { case NOTIFICATIONS: case NOTIFICATIONS_COUNT: if (!newState.notifications) { @@ -173,7 +225,7 @@ public class TbEntityLocalSubsInfo { break; } } - if (newState.equals(oldState)) { + if (newState.equals(state)) { return null; } else { this.state = newState; @@ -196,7 +248,7 @@ public class TbEntityLocalSubsInfo { public boolean isEmpty() { - return state.isEmpty(); + return subs.isEmpty(); } public TbSubscription registerPendingSubscription(TbSubscription subscription, TbEntitySubEvent event) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java b/application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java index 7714cf80fe..30a22fe5ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java @@ -70,8 +70,12 @@ public class DefaultGitSyncService implements GitSyncService { try { log.debug("[{}] Fetching repository", key); - repository.fetch(); - onUpdate(key); + boolean updated = repository.fetch(); + if (updated) { + onUpdate(key); + } else { + log.debug("[{}] No changes in the repository", key); + } } catch (Throwable e) { log.error("[{}] Failed to fetch repository", key, e); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java index 817ded33e7..73182587fb 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java @@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.UserActivationLink; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.SecuritySettings; @@ -67,31 +68,30 @@ public class AuthControllerTest extends AbstractControllerTest { .andExpect(status().isUnauthorized()); loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); loginTenantAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(TENANT_ADMIN_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.TENANT_ADMIN); + assertThat(user.getEmail()).isEqualTo(TENANT_ADMIN_EMAIL); loginCustomerUser(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.CUSTOMER_USER.name()))) - .andExpect(jsonPath("$.email", is(CUSTOMER_USER_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.CUSTOMER_USER); + assertThat(user.getEmail()).isEqualTo(CUSTOMER_USER_EMAIL); + user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); + assertThat(user.getAdditionalInfo().get("lastLoginTs").asLong()).isCloseTo(System.currentTimeMillis(), within(10000L)); } @Test public void testLoginLogout() throws Exception { loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); TimeUnit.SECONDS.sleep(1); //We need to make sure that event for invalidating token was successfully processed @@ -102,19 +102,45 @@ public class AuthControllerTest extends AbstractControllerTest { resetTokens(); } + @Test + public void testFailedLogin() throws Exception { + int maxFailedLoginAttempts = 3; + loginSysAdmin(); + updateSecuritySettings(securitySettings -> { + securitySettings.setMaxFailedLoginAttempts(maxFailedLoginAttempts); + }); + loginTenantAdmin(); + + for (int i = 0; i < maxFailedLoginAttempts; i++) { + String error = getErrorMessage(doPost("/api/auth/login", + new LoginRequest(CUSTOMER_USER_EMAIL, "IncorrectPassword")) + .andExpect(status().isUnauthorized())); + assertThat(error).containsIgnoringCase("invalid username or password"); + } + + User user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); + + String error = getErrorMessage(doPost("/api/auth/login", + new LoginRequest(CUSTOMER_USER_EMAIL, "IncorrectPassword4")) + .andExpect(status().isUnauthorized())); + assertThat(error).containsIgnoringCase("account is locked"); + + user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isFalse(); + } + @Test public void testRefreshToken() throws Exception { loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); refreshToken(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); } @Test @@ -277,6 +303,14 @@ public class AuthControllerTest extends AbstractControllerTest { doPost("/api/admin/securitySettings", securitySettings).andExpect(status().isOk()); } + private User getCurrentUser() throws Exception { + return doGet("/api/auth/user", User.class); + } + + private User getUser(UserId id) throws Exception { + return doGet("/api/user/" + id, User.class); + } + private String getActivationLink(User user) throws Exception { return doGet("/api/user/" + user.getId() + "/activationLink", String.class); } diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java index 63703ae27b..7fa848f95d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java @@ -319,7 +319,9 @@ public class TwoFactorAuthTest extends AbstractControllerTest { assertThat(successfulLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.SUCCESS); assertThat(successfulLogInAuditLog.getUserName()).isEqualTo(username); }); - assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() + + loginTenantAdmin(); + assertThat(doGet("/api/user/" + user.getId(), User.class).getAdditionalInfo() .get("lastLoginTs").asLong()) .isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3)); } diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java index 17085f3ca9..c42ecc4545 100644 --- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java @@ -113,6 +113,7 @@ public class UserControllerTest extends AbstractControllerTest { Assert.assertEquals(email, savedUser.getEmail()); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); + foundUser.setAdditionalInfo(savedUser.getAdditionalInfo()); Assert.assertEquals(foundUser, savedUser); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundUser, foundUser, @@ -265,6 +266,7 @@ public class UserControllerTest extends AbstractControllerTest { User savedUser = doPost("/api/user", user, User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); Assert.assertNotNull(foundUser); + foundUser.setAdditionalInfo(savedUser.getAdditionalInfo()); Assert.assertEquals(savedUser, foundUser); } diff --git a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java index 1c3b5ccb7a..52204f8fde 100644 --- a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java @@ -47,7 +47,7 @@ public class UserEdgeTest extends AbstractEdgeTest { @Test public void testCreateUpdateDeleteTenantUser() throws Exception { // create user - edgeImitator.expectMessageAmount(6); + edgeImitator.expectMessageAmount(3); User newTenantAdmin = new User(); newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); newTenantAdmin.setTenantId(tenantId); @@ -55,9 +55,9 @@ public class UserEdgeTest extends AbstractEdgeTest { newTenantAdmin.setFirstName("Boris"); newTenantAdmin.setLastName("Johnson"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user) - Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user) + Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); + Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); @@ -133,7 +133,7 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForMessages()); // create user - edgeImitator.expectMessageAmount(6); + edgeImitator.expectMessageAmount(3); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); customerUser.setTenantId(tenantId); @@ -142,9 +142,9 @@ public class UserEdgeTest extends AbstractEdgeTest { customerUser.setFirstName("John"); customerUser.setLastName("Edwards"); User savedCustomerUser = createUser(customerUser, "customer"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user) - Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user) + Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); + Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); diff --git a/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java new file mode 100644 index 0000000000..e9b95ec832 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java @@ -0,0 +1,186 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.subscription; + +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TbEntityLocalSubsInfoTest { + + @Test + public void addTest() { + Set expectedSubs = new HashSet<>(); + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + expectedSubs.add(attrSubscription1); + TbEntitySubEvent created = subsInfo.add(attrSubscription1); + assertFalse(subsInfo.isEmpty()); + assertNotNull(created); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(created, expectedSubs, ComponentLifecycleEvent.CREATED); + + assertNull(subsInfo.add(attrSubscription1)); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + expectedSubs.add(attrSubscription2); + TbEntitySubEvent updated = subsInfo.add(attrSubscription2); + assertNotNull(updated); + + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(updated, expectedSubs, ComponentLifecycleEvent.UPDATED); + } + + @Test + public void removeTest() { + Set expectedSubs = new HashSet<>(); + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + + expectedSubs.add(attrSubscription1); + expectedSubs.add(attrSubscription2); + + subsInfo.add(attrSubscription1); + subsInfo.add(attrSubscription2); + + assertEquals(expectedSubs, subsInfo.getSubs()); + + TbEntitySubEvent updatedEvent = subsInfo.remove(attrSubscription1); + expectedSubs.remove(attrSubscription1); + assertNotNull(updatedEvent); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(updatedEvent, expectedSubs, ComponentLifecycleEvent.UPDATED); + + TbEntitySubEvent deletedEvent = subsInfo.remove(attrSubscription2); + expectedSubs.remove(attrSubscription2); + assertNotNull(deletedEvent); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(deletedEvent, expectedSubs, ComponentLifecycleEvent.DELETED); + + assertTrue(subsInfo.isEmpty()); + } + + @Test + public void removeAllTest() { + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + + TbAttributeSubscription attrSubscription3 = TbAttributeSubscription.builder() + .sessionId("session3") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key5", 5L, "key6", 6L)) + .build(); + + subsInfo.add(attrSubscription1); + subsInfo.add(attrSubscription2); + subsInfo.add(attrSubscription3); + + assertFalse(subsInfo.isEmpty()); + + TbEntitySubEvent updatedEvent = subsInfo.removeAll(List.of(attrSubscription1, attrSubscription2)); + assertNotNull(updatedEvent); + checkEvent(updatedEvent, Set.of(attrSubscription3), ComponentLifecycleEvent.UPDATED); + + assertFalse(subsInfo.isEmpty()); + + TbEntitySubEvent deletedEvent = subsInfo.removeAll(List.of(attrSubscription3)); + assertNotNull(deletedEvent); + checkEvent(deletedEvent, null, ComponentLifecycleEvent.DELETED); + + assertTrue(subsInfo.isEmpty()); + } + + private TbEntityLocalSubsInfo createSubsInfo() { + return new TbEntityLocalSubsInfo(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID())); + } + + private void checkEvent(TbEntitySubEvent event, Set expectedSubs, ComponentLifecycleEvent expectedType) { + assertEquals(expectedType, event.getType()); + TbSubscriptionsInfo info = event.getInfo(); + if (event.getType() == ComponentLifecycleEvent.DELETED) { + assertNull(info); + return; + } + assertNotNull(info); + assertFalse(info.notifications); + assertFalse(info.alarms); + assertFalse(info.attrAllKeys); + assertFalse(info.tsAllKeys); + assertNull(info.tsKeys); + assertEquals(getAttrKeys(expectedSubs), info.attrKeys); + } + + private Set getAttrKeys(Set attributeSubscriptions) { + return attributeSubscriptions.stream().map(s -> s.getKeyStates().keySet()).flatMap(Collection::stream).collect(Collectors.toSet()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index 384bfe4772..2c5c5a618e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -114,6 +114,13 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client, false, protobuf); CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + String awaitAlias = "await Two Way Rpc (client.getObserveRelation)"; + await(awaitAlias) + .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS) + .until(() -> processTwoWayRpcTestWithAwait(callbackCoap, observeRelation, expectedResponseResult)); + } + + private boolean processTwoWayRpcTestWithAwait(CoapTestCallback callbackCoap, CoapObserveRelation observeRelation, String expectedResponseResult) throws Exception { String awaitAlias = "await Two Way Rpc (client.getObserveRelation)"; await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) @@ -146,7 +153,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC validateTwoWayStateChangedNotification(callbackCoap, expectedResponseResult, actualResult); observeRelation.proactiveCancel(); - assertTrue(observeRelation.isCanceled()); + return observeRelation.isCanceled(); } protected void processOnLoadResponse(CoapResponse response, CoapTestClient client) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index af5be0dc77..6d25701703 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -21,15 +21,16 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.server.registration.Registration; import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.HttpStatus; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -54,6 +55,7 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingC import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -70,9 +72,9 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.AbstractTransportIntegrationTest; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import java.io.IOException; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; @@ -88,6 +90,8 @@ import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.client.object.Security.noSec; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; @@ -107,7 +111,10 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportIntegrationTest { @SpyBean - LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; + protected LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; + + @SpyBean + protected DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; @Autowired private LwM2mClientContext clientContextTest; @@ -117,7 +124,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final int securityPort = 5686; public static final int portBs = 5687; public static final int securityPortBs = 5688; - public static final int[] SERVERS_PORT_NUMBERS = {port, securityPort, portBs, securityPortBs}; public static final String host = "localhost"; public static final String hostBs = "localhost"; @@ -172,12 +178,10 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected final Set expectedStatusesRegistrationLwm2mSuccessUpdate = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS, ON_UPDATE_STARTED, ON_UPDATE_SUCCESS)); protected final Set expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); - protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; protected LwM2MTestClient lwM2MTestClient; private String[] resources; protected String deviceId; - protected boolean isWriteAttribute = false; protected boolean supportFormatOnly_SenMLJSON_SenMLCBOR = false; @Before @@ -186,14 +190,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } @After - public void after() { - clientDestroy(); - executor.shutdownNow(); - } - - @AfterClass - public static void afterClass() { - awaitServersDestroy(); + public void after() throws Exception { + this.clientDestroy(true); + if (executor != null && !executor.isShutdown()) { + executor.shutdownNow(); + } } private void init() throws Exception { @@ -218,8 +219,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte String endpoint, boolean queueMode) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); - Device device = createDevice(deviceCredentials, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); @@ -233,9 +234,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, null, false, endpoint, null, queueMode); - deviceId = device.getId().getId().toString(); - awaitObserveReadAll(0, deviceId); + this.createNewClient(security, null, false, endpoint, null, queueMode, device.getId().getId().toString()); + awaitObserveReadAll(0, lwM2MTestClient.getDeviceIdStr()); String msg = getWsClient().waitForUpdate(); EntityDataUpdate update = JacksonUtil.fromString(msg, EntityDataUpdate.class); @@ -255,29 +255,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } - protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { - deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M"); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTenantId(tenantId); - deviceProfile.setTransportType(DeviceTransportType.LWM2M); - deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); - deviceProfile.setDescription(deviceProfile.getName()); + protected DeviceProfile createLwm2mDeviceProfile(String name, Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { + DeviceProfile lwm2mDeviceProfile = new DeviceProfile(); + lwm2mDeviceProfile.setName(name); + lwm2mDeviceProfile.setType(DeviceProfileType.DEFAULT); + lwm2mDeviceProfile.setTenantId(tenantId); + lwm2mDeviceProfile.setTransportType(DeviceTransportType.LWM2M); + lwm2mDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + lwm2mDeviceProfile.setDescription(name); DeviceProfileData deviceProfileData = new DeviceProfileData(); deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); deviceProfileData.setTransportConfiguration(transportConfiguration); - deviceProfile.setProfileData(deviceProfileData); + lwm2mDeviceProfile.setProfileData(deviceProfileData); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertNotNull(deviceProfile); + lwm2mDeviceProfile = doPost("/api/deviceProfile", lwm2mDeviceProfile, DeviceProfile.class); + Assert.assertNotNull(lwm2mDeviceProfile); + return lwm2mDeviceProfile; } - protected Device createDevice(LwM2MDeviceCredentials credentials, String endpoint) throws Exception { + protected Device createLwm2mDevice(LwM2MDeviceCredentials credentials, String endpoint, DeviceProfileId deviceProfileId) throws Exception { Device device = new Device(); device.setName(endpoint); - device.setDeviceProfileId(deviceProfile.getId()); + device.setDeviceProfileId(deviceProfileId); device.setTenantId(tenantId); device = doPost("/api/device", device, Device.class); Assert.assertNotNull(device); @@ -302,33 +303,37 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } public void createNewClient(Security security, Security securityBs, boolean isRpc, - String endpoint) throws Exception { - this.createNewClient(security, securityBs, isRpc, endpoint, null, false); + String endpoint, String deviceIdStr) throws Exception { + this.createNewClient(security, securityBs, isRpc, endpoint, null, false, deviceIdStr); } public void createNewClient(Security security, Security securityBs, boolean isRpc, - String endpoint, Integer clientDtlsCidLength) throws Exception { - this.createNewClient(security, securityBs, isRpc, endpoint, clientDtlsCidLength, false); + String endpoint, Integer clientDtlsCidLength, String deviceIdStr) throws Exception { + this.createNewClient(security, securityBs, isRpc, endpoint, clientDtlsCidLength, false, deviceIdStr); } public void createNewClient(Security security, Security securityBs, boolean isRpc, - String endpoint, Integer clientDtlsCidLength, boolean queueMode) throws Exception { - this.clientDestroy(); + String endpoint, Integer clientDtlsCidLength, boolean queueMode, String deviceIdStr) throws Exception { + this.clientDestroy(false); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, securityBs, clientPort, isRpc, - this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute, + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR); } + lwM2MTestClient.setDeviceIdStr(deviceIdStr); } - private void clientDestroy() { + private void clientDestroy(boolean isAfter) { try { if (lwM2MTestClient != null) { + if (isAfter) { + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); + awaitDeleteDevice(lwM2MTestClient.getDeviceIdStr()); + } lwM2MTestClient.destroy(); - awaitClientDestroy(lwM2MTestClient.getLeshanClient()); } } catch (Exception e) { log.error("Failed client Destroy", e); @@ -385,36 +390,20 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return credentials; } - private static void awaitServersDestroy() { - await("One of servers ports number is not free") - .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> isServerPortsAvailable() == null); - } - - private static String isServerPortsAvailable() { - for (int port : SERVERS_PORT_NUMBERS) { - try (ServerSocket serverSocket = new ServerSocket(port)) { - serverSocket.close(); - Assert.assertEquals(true, serverSocket.isClosed()); - } catch (IOException e) { - log.warn(String.format("Port %n still in use", port)); - return (String.format("Port %n still in use", port)); - } - } - return null; - } - - private static void awaitClientDestroy(LeshanClient leshanClient) { - await("Destroy LeshanClient: delete All is registered Servers.") - .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> leshanClient.getRegisteredServers().size() == 0); - } - protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { await("ObserveReadAll: countObserve " + cntObserve) .atMost(40, TimeUnit.SECONDS) .until(() -> cntObserve == getCntObserveAll(deviceIdStr)); } + protected void awaitDeleteDevice(String deviceIdStr) throws Exception { + await("Delete device with id: " + deviceIdStr) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + doDelete("/api/device/" + deviceIdStr) + .andExpect(status().isOk()); + return HttpStatus.NOT_FOUND.value() == doGet("/api/device/" + deviceIdStr).andReturn().getResponse().getStatus(); + }); + } protected Integer getCntObserveAll(String deviceIdStr) throws Exception { String actualResult = sendObserveOK("ObserveReadAll", null, deviceIdStr); @@ -428,7 +417,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte String actualResultCancelAll = sendObserveOK("ObserveCancelAll", null, deviceIdStr); ObjectNode rpcActualResultCancelAll = JacksonUtil.fromString(actualResultCancelAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); - awaitObserveReadAll(0, deviceId); + awaitObserveReadAll(0, lwM2MTestClient.getDeviceIdStr()); } protected String sendRpcObserveOkWithResultValue(String method, String params) throws Exception { @@ -438,7 +427,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return rpcActualResult.get("value").asText(); } protected String sendRpcObserveOk(String method, String params) throws Exception { - return sendObserveOK(method, params, deviceId); + return sendObserveOK(method, params, lwM2MTestClient.getDeviceIdStr()); } protected String sendObserveOK(String method, String params, String deviceIdStr) throws Exception { String sendRpcRequest; @@ -456,4 +445,15 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); } + protected long countUpdateReg() { + return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) + .getInvocations().stream() + .filter(invocation -> invocation.getMethod().getName().equals("updatedReg")) + .count(); + } + + protected void awaitUpdateReg(int cntUpdate) { + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updatedReg(Mockito.any(Registration.class)); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java index 73f8346566..ad23ed9f0c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java @@ -49,13 +49,13 @@ public class LwM2mAttributesTest { @ParameterizedTest(name = "Tests {index} : {0}") @MethodSource("doesntSupportAttributesWithoutValue") public void check_attribute_can_not_be_created_without_value(LwM2mAttributeModel model) { - assertThrows(UnsupportedOperationException.class, () -> LwM2mAttributes.create(model)); + assertThrows(IllegalArgumentException.class, () -> LwM2mAttributes.create(model)); } @ParameterizedTest(name = "Tests {index} : {0}") @MethodSource("doesntSupportAttributesWithValueNull") public void check_attribute_can_not_be_created_with_null(LwM2mAttributeModel model) { - assertThrows(NullPointerException.class, () -> LwM2mAttributes.create(model, null)); + assertThrows(IllegalArgumentException.class, () -> LwM2mAttributes.create(model, null)); } private static Stream supportNullAttributes() throws InvalidAttributeException { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 655edc6db6..387085c511 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -137,9 +137,12 @@ public class LwM2MTestClient { private Map clientDtlsCid; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; + private LwM2mTemperatureSensor lwM2mTemperatureSensor12; + private String deviceIdStr; + public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, - LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode, + LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode, boolean supportFormatOnly_SenMLJSON_SenMLCBOR) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; @@ -149,7 +152,7 @@ public class LwM2MTestClient { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } LwM2mModel model = new StaticModel(models); - ObjectsInitializer initializer = isWriteAttribute ? new TbObjectsInitializer(model) : new ObjectsInitializer(model); + ObjectsInitializer initializer = new ObjectsInitializer(model); if (securityBs != null && security != null) { // SECURITY security.setId(serverId); @@ -189,7 +192,7 @@ public class LwM2MTestClient { locationParams.getPos(); initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0); - LwM2mTemperatureSensor lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); + lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2mTemperatureSensor12); List enablers = initializer.createAll(); @@ -315,7 +318,6 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); - lwM2mTemperatureSensor12.setLeshanClient(leshanClient); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -452,6 +454,7 @@ public class LwM2MTestClient { if (isStartLw) { this.awaitClientAfterStartConnectLw(); } + lwM2mTemperatureSensor12.setLeshanClient(leshanClient); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 3efd6365dd..9c5b63bc3d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -129,7 +129,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements fireResourceChange(resourceId); return WriteResponse.success(); } else { - WriteResponse.badRequest("Invalidate value ..."); + return WriteResponse.badRequest("Invalidate value ..."); } case 1: setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 4f594ed4c0..9f96956a49 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -50,14 +50,17 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private double maxMeasuredValue = currentTemp; private LeshanClient leshanClient; - private int cntRead_5700; private int cntIdentitySystem; protected static final Random RANDOM = new Random(); private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); - public LwM2mTemperatureSensor() { + private LwM2mServer registeredServer; + private ManualDataSender sender; + + private int resourceIdForSendCollected = 5700; + public LwM2mTemperatureSensor() { } public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { @@ -72,26 +75,33 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr @Override public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { - log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + log.trace("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + if (this.registeredServer == null && this.leshanClient != null && getId() == 12) { + try { + Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); + this.registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); + this.sender = (ManualDataSender) this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME); + this.sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); + } catch (Exception e) { + log.error("[{}] Sender for SendCollected", e.toString()); + e.printStackTrace(); + } + } switch (resourceId) { case 5601: return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); case 5602: return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); case 5700: - if (identity == LwM2mServer.SYSTEM) { // return value for ForCollectedValue + if (identity == LwM2mServer.SYSTEM) { + double val5700 = cntIdentitySystem == 0 ? RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1; cntIdentitySystem++; - return ReadResponse.success(resourceId, cntIdentitySystem == 1 ? - RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1); - } - cntRead_5700++; - if (cntRead_5700 == 1) { // read value after start - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return ReadResponse.success(resourceId, val5700); } else { - if (this.getId() == 12 && this.leshanClient != null) { + if (cntIdentitySystem == 1 && this.getId() == 12 && this.leshanClient != null) { sendCollected(); } - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return super.read(identity, resourceId); } case 5701: return ReadResponse.success(resourceId, UNIT_CELSIUS); @@ -163,14 +173,10 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private void sendCollected() { try { - int resourceId = 5700; - LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); - ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, - ManualDataSender.class); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); - Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + if ((Instant.now().toEpochMilli() - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0) < RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) { + Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); + } + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1 = Instant.now().toEpochMilli(); sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); } catch (InterruptedException e) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 370c2249df..a6a850a897 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -20,7 +20,7 @@ import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -30,7 +30,6 @@ import org.eclipse.leshan.core.response.WriteResponse; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PrimitiveIterator; @@ -46,9 +45,46 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final Random RANDOM = new Random(); private static final int min = 5; private static final int max = 50; - private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator(); + private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min, max + 1).iterator(); private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); - + /** + * 0: DC power + * 1: Internal Battery + * 2: External Battery + * 3: Fuel Cell + * 4: Power over Ethernet + * 5: USB + * 6: AC (Mains) power + * 7: Solar + */ + private static final Map availablePowerSources = + Map.of(0, 0L, 1, 1L, 2, 7L); + private static Map powerSourceVoltage = + Map.of(0, 12000L, 1, 12400L, 7, 14600L); //mV + private static Map powerSourceCurrent = + Map.of(0, 72000L, 1, 2000L, 7, 25000L); // mA + + /** + * 0=No error + * 1=Low battery power + * 2=External power supply off + * 3=GPS module failure + * 4=Low received signal strength + * 5=Out of memory + * 6=SMS failure + * 7=IP connectivity failure + * 8=Peripheral malfunction + * 9..15=Reserved for future use + * 16..32=Device specific error codes + * + * When the single Device Object Instance is initiated, there is only one error code Resource Instance whose value is equal to 0 that means no error. + * When the first error happens, the LwM2M Client changes error code Resource Instance to any non-zero value to indicate the error type. + * When any other error happens, a new error code Resource Instance is created. + * When an error associated with a Resource Instance is no longer present, that Resource Instance is deleted. + * When the single existing error is no longer present, the LwM2M Client returns to the original no error state where Instance 0 has value 0. + */ + private static Map errorCode = + Map.of(0, 0L); // 0-32 public SimpleLwM2MDevice() { } @@ -81,15 +117,17 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl case 3: return ReadResponse.success(resourceId, getFirmwareVersion()); case 6: - return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER); + return ReadResponse.success(resourceId, getAvailablePowerSources(), Type.INTEGER); + case 7: + return ReadResponse.success(resourceId, getPowerSourceVoltage(), Type.INTEGER); + case 8: + return ReadResponse.success(resourceId, getPowerSourceCurrent(), Type.INTEGER); case 9: return ReadResponse.success(resourceId, getBatteryLevel()); case 10: return ReadResponse.success(resourceId, getMemoryFree()); case 11: - Map errorCodes = new HashMap<>(); - errorCodes.put(0, getErrorCode()); - return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER); + return ReadResponse.success(resourceId, getErrorCodes(), Type.INTEGER); case 14: return ReadResponse.success(resourceId, getUtcOffset()); case 15: @@ -156,16 +194,19 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return "1.0.2"; } - private long getErrorCode() { - return 0; + private Map getAvailablePowerSources() { + return availablePowerSources; } - private Map getAvailablePowerSources() { - Map availablePowerSources = new HashMap<>(); - availablePowerSources.put(0, 1L); - availablePowerSources.put(1, 2L); - availablePowerSources.put(2, 5L); - return availablePowerSources; + private Map getPowerSourceVoltage() { + return powerSourceVoltage; + } + private Map getPowerSourceCurrent() { + return powerSourceCurrent; + } + + private Map getErrorCodes() { + return errorCode; } private int getBatteryLevel() { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java deleted file mode 100644 index f548cc30e2..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java +++ /dev/null @@ -1,732 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.client; - -import org.eclipse.leshan.client.LwM2mClient; -import org.eclipse.leshan.client.resource.BaseObjectEnabler; -import org.eclipse.leshan.client.resource.DummyInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; -import org.eclipse.leshan.client.resource.listener.ResourceListener; -import org.eclipse.leshan.client.servers.LwM2mServer; -import org.eclipse.leshan.client.servers.ServersInfoExtractor; -import org.eclipse.leshan.client.util.LinkFormatHelper; -import org.eclipse.leshan.core.Destroyable; -import org.eclipse.leshan.core.LwM2mId; -import org.eclipse.leshan.core.Startable; -import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.node.LwM2mResourceInstance; -import org.eclipse.leshan.core.request.BootstrapDeleteRequest; -import org.eclipse.leshan.core.request.BootstrapReadRequest; -import org.eclipse.leshan.core.request.BootstrapWriteRequest; -import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.CreateRequest; -import org.eclipse.leshan.core.request.DeleteRequest; -import org.eclipse.leshan.core.request.DiscoverRequest; -import org.eclipse.leshan.core.request.DownlinkRequest; -import org.eclipse.leshan.core.request.ExecuteRequest; -import org.eclipse.leshan.core.request.ObserveRequest; -import org.eclipse.leshan.core.request.ReadRequest; -import org.eclipse.leshan.core.request.WriteAttributesRequest; -import org.eclipse.leshan.core.request.WriteRequest; -import org.eclipse.leshan.core.request.WriteRequest.Mode; -import org.eclipse.leshan.core.response.BootstrapDeleteResponse; -import org.eclipse.leshan.core.response.BootstrapReadResponse; -import org.eclipse.leshan.core.response.BootstrapWriteResponse; -import org.eclipse.leshan.core.response.CreateResponse; -import org.eclipse.leshan.core.response.DeleteResponse; -import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ExecuteResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.response.WriteAttributesResponse; -import org.eclipse.leshan.core.response.WriteResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable { - - private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); - - protected Map instances; - - protected LwM2mInstanceEnablerFactory instanceFactory; - protected ContentFormat defaultContentFormat; - - private LinkFormatHelper tbLinkFormatHelper; - protected Map lwM2mAttributes; - public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map instances, - LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { - super(id, objectModel); - this.instances = new HashMap<>(instances); - ; - this.instanceFactory = instanceFactory; - this.defaultContentFormat = defaultContentFormat; - for (Entry entry : this.instances.entrySet()) { - instances.put(entry.getKey(), entry.getValue()); - listenInstance(entry.getValue(), entry.getKey()); - } - this.lwM2mAttributes = new HashMap<>(); - } - - public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { - super(id, objectModel); - } - - @Override - public synchronized List getAvailableInstanceIds() { - List ids = new ArrayList<>(instances.keySet()); - Collections.sort(ids); - return ids; - } - - @Override - public synchronized List getAvailableResourceIds(int instanceId) { - LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId); - if (instanceEnabler != null) { - return instanceEnabler.getAvailableResourceIds(getObjectModel()); - } else { - return Collections.emptyList(); - } - } - - public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) { - instances.put(instanceId, newInstance); - listenInstance(newInstance, instanceId); - fireInstancesAdded(instanceId); - } - - public synchronized LwM2mInstanceEnabler getInstance(int instanceId) { - return instances.get(instanceId); - } - - public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) { - LwM2mInstanceEnabler removedInstance = instances.remove(instanceId); - if (removedInstance != null) { - fireInstancesRemoved(removedInstance.getId()); - } - return removedInstance; - } - - @Override - protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) { - if (!getObjectModel().multiple && instances.size() > 0) { - return CreateResponse.badRequest("an instance already exist for this single instance object"); - } - - if (request.unknownObjectInstanceId()) { - // create instance - LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0, - request.getResources()); - - // add new instance to this object - instances.put(newInstance.getId(), newInstance); - listenInstance(newInstance, newInstance.getId()); - fireInstancesAdded(newInstance.getId()); - - return CreateResponse - .success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString()); - } else { - List instanceNodes = request.getObjectInstances(); - - // checks single object instances - if (!getObjectModel().multiple) { - if (request.getObjectInstances().size() > 1) { - return CreateResponse.badRequest("can not create several instances on this single instance object"); - } - if (request.getObjectInstances().get(0).getId() != 0) { - return CreateResponse.badRequest("single instance object must use 0 as ID"); - } - } - // ensure instance does not already exists - for (LwM2mObjectInstance instance : instanceNodes) { - if (instances.containsKey(instance.getId())) { - return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId())); - } - } - - // create the new instances - int[] instanceIds = new int[request.getObjectInstances().size()]; - int i = 0; - for (LwM2mObjectInstance instance : request.getObjectInstances()) { - // create instance - LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(), - instance.getResources().values()); - - // add new instance to this object - instances.put(newInstance.getId(), newInstance); - listenInstance(newInstance, newInstance.getId()); - - // store instance ids - instanceIds[i] = newInstance.getId(); - i++; - } - fireInstancesAdded(instanceIds); - return CreateResponse.success(); - } - } - - protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId, - Collection resources) { - // create the new instance - LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet()); - newInstance.setLwM2mClient(getLwm2mClient()); - - // add/write resource - for (LwM2mResource resource : resources) { - newInstance.write(server, true, resource.getId(), resource); - } - - return newInstance; - } - - @Override - protected ReadResponse doRead(LwM2mServer server, ReadRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - List lwM2mObjectInstances = new ArrayList<>(); - for (LwM2mInstanceEnabler instance : instances.values()) { - ReadResponse response = instance.read(server); - if (response.isSuccess()) { - lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); - } - } - return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); - } - - // Manage Instance case - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return ReadResponse.notFound(); - - if (path.getResourceId() == null) { - return instance.read(server); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.read(server, path.getResourceId()); - } - - // Manage Resource Instance case - return instance.read(server, path.getResourceId(), path.getResourceInstanceId()); - } - - @Override - protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) { - // Basic implementation we delegate to classic Read Request - ReadResponse response = doRead(server, - new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); - return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); - } - - @Override - protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) { - final LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - List lwM2mObjectInstances = new ArrayList<>(); - for (LwM2mInstanceEnabler instance : instances.values()) { - ReadResponse response = instance.observe(server); - if (response.isSuccess()) { - lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); - } - } - return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); - } - - // Manage Instance case - final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return ObserveResponse.notFound(); - - if (path.getResourceId() == null) { - return instance.observe(server); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.observe(server, path.getResourceId()); - } - - // Manage Resource Instance case - return instance.observe(server, path.getResourceId(), path.getResourceInstanceId()); - } - - @Override - protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Instance case - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return WriteResponse.notFound(); - - if (path.isObjectInstance()) { - return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode()); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.write(server, request.isReplaceRequest(), path.getResourceId(), - (LwM2mResource) request.getNode()); - } - - // Manage Resource Instance case - return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(), - ((LwM2mResourceInstance) request.getNode())); - } - - @Override - protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) { - LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); - } else { - doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(), - instanceNode.getResources().values())); - } - } - return BootstrapWriteResponse.success(); - } - - // Manage Instance case - if (path.isObjectInstance()) { - LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode(); - LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); - } else { - doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(), - path.getObjectInstanceId(), instanceNode.getResources().values())); - } - return BootstrapWriteResponse.success(); - } - - // Manage resource case - LwM2mResource resource = (LwM2mResource) request.getNode(); - LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), - new LwM2mObjectInstance(path.getObjectInstanceId(), resource))); - } else { - instanceEnabler.write(server, true, path.getResourceId(), resource); - } - return BootstrapWriteResponse.success(); - } - - @Override - protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) { - LwM2mPath path = request.getPath(); - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) { - return ExecuteResponse.notFound(); - } - return instance.execute(server, path.getResourceId(), request.getArguments()); - } - - @Override - protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) { - LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId()); - if (deletedInstance != null) { - deletedInstance.onDelete(server); - fireInstancesRemoved(deletedInstance.getId()); - return DeleteResponse.success(); - } - return DeleteResponse.notFound(); - } - - @Override - public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) { - if (request.getPath().isRoot() || request.getPath().isObject()) { - if (id == LwM2mId.SECURITY) { - // For security object, we clean everything except bootstrap Server account. - - // Get bootstrap account and store removed instances ids - Entry bootstrapServerAccount = null; - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) { - bootstrapServerAccount = instance; - } else { - // Store instance ids - instanceIds[i] = instance.getKey(); - i++; - } - } - // Clear everything - instances.clear(); - - // Put bootstrap account again - if (bootstrapServerAccount != null) { - instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue()); - } - - fireInstancesRemoved(instanceIds); - return BootstrapDeleteResponse.success(); - } else if (id == LwM2mId.OSCORE) { - // For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account. - - // Get bootstrap account - LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( - getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); - // Get OSCORE instance ID associated to it - Integer bootstrapOscoreInstanceId = bootstrapInstance != null - ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) - : null; - - // if bootstrap server use OSCORE, - // search the OSCORE instance for this ID and store removed instances ids - if (bootstrapOscoreInstanceId != null) { - Entry bootstrapServerOscore = null; - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - if (bootstrapOscoreInstanceId.equals(instance.getKey())) { - bootstrapServerOscore = instance; - } else { - // Store instance ids - instanceIds[i] = instance.getKey(); - i++; - } - } - - // Clear everything - instances.clear(); - - // Put bootstrap OSCORE instance again - if (bootstrapServerOscore != null) { - instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue()); - } - fireInstancesRemoved(instanceIds); - return BootstrapDeleteResponse.success(); - } - // else delete everything. - } - - // In all other cases, just delete everything - instances.clear(); - // fired instances removed - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - instanceIds[i] = instance.getKey(); - i++; - } - fireInstancesRemoved(instanceIds); - - return BootstrapDeleteResponse.success(); - } else if (request.getPath().isObjectInstance()) { - if (id == LwM2mId.SECURITY) { - // For security object, deleting bootstrap Server account is not allowed - LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId()); - if (instance == null) { - return BootstrapDeleteResponse - .badRequest(String.format("Instance %s not found", request.getPath())); - } else if (ServersInfoExtractor.isBootstrapServer(instance)) { - return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted"); - } - } else if (id == LwM2mId.OSCORE) { - // For OSCORE object, deleting instance linked to Bootstrap account is not allowed - - // Get bootstrap instance - LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( - getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); - // Get OSCORE instance ID associated to it - Integer bootstrapOscoreInstanceId = bootstrapInstance != null - ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) - : null; - - if (bootstrapOscoreInstanceId != null - && bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) { - return BootstrapDeleteResponse - .badRequest("OSCORE instance linked to bootstrap server can not be deleted"); - } - } - if (null != instances.remove(request.getPath().getObjectInstanceId())) { - fireInstancesRemoved(request.getPath().getObjectInstanceId()); - return BootstrapDeleteResponse.success(); - } else { - return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath())); - } - } - return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath())); - } - - protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) { - instance.addResourceListener(new ResourceListener() { - @Override - public void resourceChanged(LwM2mPath... paths) { - for (LwM2mPath path : paths) { - if (!isValid(instanceId, path)) { - LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.", - instanceId, getId(), path); - } - } - fireResourcesChanged(paths); - } - }); - } - - protected boolean isValid(int instanceId, LwM2mPath pathToValidate) { - if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance())) - return false; - - if (pathToValidate.getObjectId() != getId()) { - return false; - } - - if (pathToValidate.getObjectInstanceId() != instanceId) { - return false; - } - - return true; - } - - @Override - public ContentFormat getDefaultEncodingFormat(DownlinkRequest request) { - return defaultContentFormat; - } - - @Override - public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) { - super.init(client, linkFormatHelper); - this.tbLinkFormatHelper = linkFormatHelper; - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - instanceEnabler.setLwM2mClient(client); - } - } - - @Override - public void destroy() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Destroyable) { - ((Destroyable) instanceEnabler).destroy(); - } else if (instanceEnabler instanceof Stoppable) { - ((Stoppable) instanceEnabler).stop(); - } - } - } - - @Override - public void start() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Startable) { - ((Startable) instanceEnabler).start(); - } - } - } - - @Override - public void stop() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Stoppable) { - ((Stoppable) instanceEnabler).stop(); - } - } - } - - @Override - public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) { - // execute is not supported for bootstrap - if (server.isLwm2mBootstrapServer()) { - return WriteAttributesResponse.methodNotAllowed(); - } -// return WriteAttributesResponse.internalServerError("not implemented"); - return doWriteAttributes(server, request); - } - - /** - * Class Attributes - * - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource - * - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource - * - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource - * - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource - * - Step st (def = -- ) Float Resource Numerical&Readable Resource - */ - public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { - LwM2mPath lwM2mPath = request.getPath(); - LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); - Map > attributes = new HashMap<>(); - - for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { - if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { - if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) { - attributes.put(attr.getName(), attr); - } else { - return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object."); - } - } else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) { - if (lwM2mPath.isResource()) { - attributes.put(attr.getName(), attr); - } else { - return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource."); - } - } - } - if (attributes.size()>0){ - if (attributeSet == null) { - attributeSet = new LwM2mAttributeSet(attributes.values()); - } else { - Iterable> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); - Map > attributesOld = new HashMap<>(); - for (LwM2mAttribute attr : lwM2mAttributeIterable) { - attributesOld.put(attr.getName(), attr); - } - attributesOld.putAll(attributes); - attributeSet = new LwM2mAttributeSet(attributesOld.values()); - } - lwM2mAttributes.put(lwM2mPath, attributeSet); - return WriteAttributesResponse.success(); - } - return WriteAttributesResponse.internalServerError("not implemented"); - } - - @Override - public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { - - if (server.isLwm2mBootstrapServer()) { - // discover is not supported for bootstrap - return DiscoverResponse.methodNotAllowed(); - } - - if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { - return DiscoverResponse.notFound(); - } - return doDiscover(server, request); - - } - - protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) { - - LwM2mPath path = request.getPath(); - if (path.isObject()) { - LwM2mLink[] ObjectLinks = linkUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(this, null), server); - return DiscoverResponse.success(ObjectLinks); - - } else if (path.isObjectInstance()) { - // Manage discover on instance - if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) - return DiscoverResponse.notFound(); - - LwM2mLink[] instanceLink = linkUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null), server); - return DiscoverResponse.success(instanceLink); - - } else if (path.isResource()) { - // Manage discover on resource - if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) - return DiscoverResponse.notFound(); - - ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); - if (resourceModel == null) - return DiscoverResponse.notFound(); - - if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) - return DiscoverResponse.notFound(); - - LwM2mLink resourceLink = linkAddAttribute( - this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null), - server); - return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); - } - return DiscoverResponse.badRequest(null); - } - - private LwM2mLink[] linkUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { - return Arrays.stream(links) - .map(link -> linkAddAttribute(link, server)) - .toArray(LwM2mLink[]::new); - } - - private LwM2mLink linkAddAttribute(LwM2mLink link, LwM2mServer server) { - - LwM2mAttributeSet lwM2mAttributeSetDop = null; - if (this.lwM2mAttributes.get(link.getPath())!= null){ - lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); - } - LwM2mAttribute resourceAttributeDim = getResourceAttributes (server, link.getPath()); - - Map > attributes = new HashMap<>(); - if (link.getAttributes() != null) { - for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); - } - } - if (lwM2mAttributeSetDop != null) { - for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); - } - } - if (resourceAttributeDim != null) { - attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); - } - return new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()); - } - - protected LwM2mAttribute getResourceAttributes (LwM2mServer server, LwM2mPath path) { - ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); - if (path.isResource() && resourceModel.multiple) { - return getResourceAttributeDim(path, server); - } - return null; - } - - protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - try { - ReadResponse readResponse = instance.read(server, path.getResourceId()); - if (readResponse.getCode().getCode()==205 && readResponse.getContent() instanceof LwM2mMultipleResource) { - long valueDim = ((LwM2mMultipleResource)readResponse.getContent()).getInstances().size(); - return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); - } else { - return null; - } - } catch (Exception e ){ - return null; - } - } - -} - diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java deleted file mode 100644 index eff5ac962e..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.client; - -import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; -import org.eclipse.leshan.client.resource.ObjectsInitializer; -import org.eclipse.leshan.core.model.LwM2mModel; -import org.eclipse.leshan.core.model.ObjectModel; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TbObjectsInitializer extends ObjectsInitializer { - - - public TbObjectsInitializer(LwM2mModel model) { - super(model); - } - - public List create(int... objectId) { - List enablers = new ArrayList<>(); - for (int anObjectId : objectId) { - LwM2mObjectEnabler objectEnabler = create(anObjectId); - if (objectEnabler != null) - enablers.add(objectEnabler); - } - return enablers; - } - - public LwM2mObjectEnabler create(int objectId) { - ObjectModel objectModel = model.getObjectModel(objectId); - if (objectModel == null) { - throw new IllegalArgumentException( - "Cannot create object for id " + objectId + " because no model is defined for this id."); - } - return createNodeEnabler(objectModel); - } - - protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) { - Map instances = new HashMap<>(); - LwM2mInstanceEnabler[] newInstances = createInstances(objectModel); - for (LwM2mInstanceEnabler instance : newInstances) { - // set id if not already set - if (instance.getId() == null) { - int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet()); - instance.setId(id); - } - instance.setModel(objectModel); - instances.put(instance.getId(), instance); - } - return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel), - getContentFormat(objectModel.id)); - } -} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java index 60e335e32a..6008c0352a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java @@ -15,17 +15,31 @@ */ package org.thingsboard.server.transport.lwm2m.ota; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +@Slf4j @DaoSqlTest public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -33,9 +47,10 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg protected static final String CLIENT_ENDPOINT_WITHOUT_FW_INFO = "WithoutFirmwareInfoDevice"; protected static final String CLIENT_ENDPOINT_OTA5 = "Ota5_Device"; protected static final String CLIENT_ENDPOINT_OTA9 = "Ota9_Device"; + protected List expectedStatuses; - protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA = + protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA5 = " {\n" + " \"keyName\": {\n" + @@ -43,22 +58,14 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg " \"/5_1.2/0/5\": \"updateResult\",\n" + " \"/5_1.2/0/6\": \"pkgname\",\n" + " \"/5_1.2/0/7\": \"pkgversion\",\n" + - " \"/5_1.2/0/9\": \"firmwareUpdateDeliveryMethod\",\n" + - " \"/9_1.1/0/0\": \"pkgname\",\n" + - " \"/9_1.1/0/1\": \"pkgversion\",\n" + - " \"/9_1.1/0/7\": \"updateState\",\n" + - " \"/9_1.1/0/9\": \"updateResult\"\n" + + " \"/5_1.2/0/9\": \"firmwareUpdateDeliveryMethod\"\n" + " },\n" + " \"observe\": [\n" + " \"/5_1.2/0/3\",\n" + " \"/5_1.2/0/5\",\n" + " \"/5_1.2/0/6\",\n" + " \"/5_1.2/0/7\",\n" + - " \"/5_1.2/0/9\",\n" + - " \"/9_1.1/0/0\",\n" + - " \"/9_1.1/0/1\",\n" + - " \"/9_1.1/0/7\",\n" + - " \"/9_1.1/0/9\"\n" + + " \"/5_1.2/0/9\"\n" + " ],\n" + " \"attribute\": [],\n" + " \"telemetry\": [\n" + @@ -66,7 +73,28 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg " \"/5_1.2/0/5\",\n" + " \"/5_1.2/0/6\",\n" + " \"/5_1.2/0/7\",\n" + - " \"/5_1.2/0/9\",\n" + + " \"/5_1.2/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + + protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9 = + + " {\n" + + " \"keyName\": {\n" + + " \"/9_1.1/0/0\": \"pkgname\",\n" + + " \"/9_1.1/0/1\": \"pkgversion\",\n" + + " \"/9_1.1/0/7\": \"updateState\",\n" + + " \"/9_1.1/0/9\": \"updateResult\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/9_1.1/0/0\",\n" + + " \"/9_1.1/0/1\",\n" + + " \"/9_1.1/0/7\",\n" + + " \"/9_1.1/0/9\"\n" + + " ],\n" + + " \"attribute\": [],\n" + + " \"telemetry\": [\n" + " \"/9_1.1/0/0\",\n" + " \"/9_1.1/0/1\",\n" + " \"/9_1.1/0/7\",\n" + @@ -79,14 +107,14 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg setResources(this.RESOURCES_OTA); } - protected OtaPackageInfo createFirmware() throws Exception { + protected OtaPackageInfo createFirmware(String version, DeviceProfileId deviceProfileId) throws Exception { String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; OtaPackageInfo firmwareInfo = new OtaPackageInfo(); - firmwareInfo.setDeviceProfileId(deviceProfile.getId()); + firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle("My firmware"); - firmwareInfo.setVersion("v1.0"); + firmwareInfo.setVersion(version); OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); @@ -95,11 +123,11 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); } - protected OtaPackageInfo createSoftware() throws Exception { + protected OtaPackageInfo createSoftware(DeviceProfileId deviceProfileId) throws Exception { String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; OtaPackageInfo swInfo = new OtaPackageInfo(); - swInfo.setDeviceProfileId(deviceProfile.getId()); + swInfo.setDeviceProfileId(deviceProfileId); swInfo.setType(SOFTWARE); swInfo.setTitle("My sw"); swInfo.setVersion("v1.0"); @@ -117,4 +145,28 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg setJwtToken(postRequest); return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); } + + + protected Device getDeviceFromAPI(UUID deviceId) throws Exception { + final Device device = doGet("/api/device/" + deviceId, Device.class); + log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device); + return device; + } + + protected List getFwSwStateTelemetryFromAPI(UUID deviceId, String type_state) throws Exception { + final List tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=" + type_state + "&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { + })); + log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); + return tsKvEntries; + } + + protected boolean predicateForStatuses(List ts) { + List statuses = ts.stream() + .sorted(Comparator.comparingLong(TsKvEntry::getTs)) + .map(KvEntry::getValueAsString) + .map(OtaPackageUpdateStatus::valueOf) + .collect(Collectors.toList()); + log.warn("{}", statuses); + return statuses.containsAll(expectedStatuses); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java similarity index 51% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java index b78e738d72..a50f7df6a2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.kv.KvEntry; @@ -29,9 +30,7 @@ import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTes import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -45,24 +44,21 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INIT import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; @Slf4j -public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { - - private List expectedStatuses; +public class Ota5LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { @Test - public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception { + public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile_IsNotSupported() throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, deviceProfile.getId()); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, device.getId().getId().toString()); awaitObserveReadAll(0, device.getId().getId().toString()); - device.setFirmwareId(createFirmware().getId()); + device.setFirmwareId(createFirmware("5.1", deviceProfile.getId()).getId()); final Device savedDevice = doPost("/api/device", device, Device.class); Thread.sleep(1000); @@ -78,81 +74,37 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { Assert.assertEquals(expectedStatuses, statuses); } + /** + * /5/0/5 -> Update Result (Res); 5/0/3 -> State; + * => ((Res>=0 && Res<=9) && State=0) + * => Write to Package/Write to Package URI -> DOWNLOADING ((Res>=0 && Res<=9) && State=1) + * => Download Finished -> DOWNLOADED ((Res==0 || Res=8) && State=2) + * => Executable resource Update is triggered / Initiate Firmware Update -> UPDATING (Res=0 && State=3) + * => Update Successful [Res==1] + * => Start / Res=0 -> "IDLE" .... + * @throws Exception + */ @Test - public void testFirmwareUpdateByObject5() throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + public void testFirmwareUpdateByObject5_Ok() throws Exception { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA5, getBootstrapServerCredentialsNoSec(NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA5, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA5); - awaitObserveReadAll(9, device.getId().getId().toString()); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5, deviceProfile.getId()); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA5, device.getId().getId().toString()); + awaitObserveReadAll(5, device.getId().getId().toString()); - device.setFirmwareId(createFirmware().getId()); + device.setFirmwareId(createFirmware("fw.v.1.5.0-update", deviceProfile.getId()).getId()); final Device savedDevice = doPost("/api/device", device, Device.class); assertThat(savedDevice).as("saved device").isNotNull(); assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); - List ts = await("await on timeseries") + List ts = await("await on timeseries for FW") .atMost(TIMEOUT, TimeUnit.SECONDS) - .until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + - savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + - System.currentTimeMillis(), new TypeReference<>() { - })), this::predicateForStatuses); + .until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "fw_state"), this::predicateForStatuses); log.warn("Object5: Got the ts: {}", ts); } - /** - * This is the example how to use the AWAITILITY instead Thread.sleep() - * Test will finish as fast as possible, but will await until TIMEOUT if a build machine is busy or slow - * Check the detailed log output to learn how Awaitility polling the API and when exactly expected result appears - * */ - @Test - public void testSoftwareUpdateByObject9() throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); - LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9); - awaitObserveReadAll(9, device.getId().getId().toString()); - - device.setSoftwareId(createSoftware().getId()); - final Device savedDevice = doPost("/api/device", device, Device.class); //sync call - - assertThat(savedDevice).as("saved device").isNotNull(); - assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - - expectedStatuses = List.of( - QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); - - List ts = await("await on timeseries") - .atMost(TIMEOUT, TimeUnit.SECONDS) - .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), this::predicateForStatuses); - log.warn("Object9: Got the ts: {}", ts); - } - - private Device getDeviceFromAPI(UUID deviceId) throws Exception { - final Device device = doGet("/api/device/" + deviceId, Device.class); - log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device); - return device; - } - - private List getSwStateTelemetryFromAPI(UUID deviceId) throws Exception { - final List tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { - })); - log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); - return tsKvEntries; - } - - private boolean predicateForStatuses(List ts) { - List statuses = ts.stream() - .sorted(Comparator.comparingLong(TsKvEntry::getTs)) - .map(KvEntry::getValueAsString) - .map(OtaPackageUpdateStatus::valueOf) - .collect(Collectors.toList()); - log.warn("{}", statuses); - return statuses.containsAll(expectedStatuses); - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java new file mode 100644 index 0000000000..68f69bc035 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.ota.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +@Slf4j +public class Ota9LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { + + /** + * => Start -> INITIAL (State=0) -> DOWNLOAD STARTED; + * => PKG / URI Write -> DOWNLOAD STARTED (Res=1 (Downloading) && State=1) -> DOWNLOADED + * => PKG Written -> DOWNLOADED (Res=1 Initial && State=2) -> DELIVERED; + * => PKG integrity verified -> DELIVERED (Res=3 (Successfully Downloaded and package integrity verified) && State=3) -> INSTALLED; + * => Install -> INSTALLED (Res=2 SW successfully installed) && State=4) -> Start + * + * */ + @Test + public void testSoftwareUpdateByObject9() throws Exception { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9, getBootstrapServerCredentialsNoSec(NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA9, transportConfiguration); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9, deviceProfile.getId()); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9, device.getId().getId().toString()); + awaitObserveReadAll(4, device.getId().getId().toString()); + + device.setSoftwareId(createSoftware(deviceProfile.getId()).getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); //sync call + + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); + + expectedStatuses = List.of( + QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); + List ts = await("await on timeseries") + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "sw_state"), this::predicateForStatuses); + log.warn("Object9: Got the ts: {}", ts); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java index ea9814c1d0..7195b0d229 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.rpc; -import org.junit.Before; import org.thingsboard.server.dao.service.DaoSqlTest; +import static org.junit.Assert.assertTrue; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest{ @@ -26,9 +26,8 @@ public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpc setResources(this.RESOURCES_RPC_MULTIPLE_19); } - @Before - public void initTest () throws Exception { - awaitObserveReadAll(4, deviceId); + protected void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { + String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); + assertTrue(rpcActualResult.contains("LwM2mSingleResource") || rpcActualResult.contains("LwM2mMultipleResource")); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index d0b86fdab0..d5c9683c39 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -22,13 +22,13 @@ import org.junit.Before; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import java.util.List; import java.util.Set; @@ -71,7 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); - protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; + protected String CONFIG_PROFILE_WITH_PARAMS_RPC; public Set expectedObjects; public Set expectedObjectIdVers; public Set expectedInstances; @@ -98,9 +98,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected String idVer_19_0_0; - @SpyBean - protected DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; - @SpyBean protected LwM2mTransportServerHelper lwM2mTransportServerHelperTest; @@ -110,18 +107,21 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg @Before public void startInitRPC() throws Exception { - if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationDiscoverWriteAttributesTest")){ - isWriteAttribute = true; - } - if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ + if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")) { supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - initRpc(); + if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationObserveTest")) { + initRpc(0); + } else if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationReadCollectedValueTest")) { + initRpc(3303); + } else { + initRpc(1); + } } - private void initRpc () throws Exception { + protected void initRpc(int typeConfigProfile) throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, null, true, endpoint); + createNewClient(SECURITY_NO_SEC, null, true, endpoint, null); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); @@ -154,18 +154,17 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg idVer_3_0_0 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9; - id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); + id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; - OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = + String ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE = " {\n" + " \"keyName\": {\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + " },\n" + " \"observe\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + @@ -180,19 +179,60 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"telemetry\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_19_0_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + String TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\"\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + idVer_3_0_9 + "\",\n" + + " \"" + idVer_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + String TELEMETRY_WITH_PARAMS_RPC_COLLECTED_VALUE = + " {\n" + + " \"keyName\": {\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + CONFIG_PROFILE_WITH_PARAMS_RPC = + switch (typeConfigProfile) { + case 0 -> ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; + case 1 -> TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE; + case 3303 -> TELEMETRY_WITH_PARAMS_RPC_COLLECTED_VALUE; + default -> throw new IllegalStateException("Unexpected value: " + typeConfigProfile); + }; + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(CONFIG_PROFILE_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint)); - final Device device = createDevice(deviceCredentials, endpoint); - deviceId = device.getId().getId().toString(); - + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); + lwM2MTestClient.setDeviceIdStr(device.getId().getId().toString()); lwM2MTestClient.start(true); } @@ -236,14 +276,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg log.trace("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get()); } - protected long countUpdateReg() { - return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) - .getInvocations().stream() - .filter(invocation -> invocation.getMethod().getName().equals("updatedReg")) - .count(); - } - - protected long countSendParametersOnThingsboardTelemetryResource(String rezName) { + protected long countSendParametersOnThingsboardTelemetryResource(String rezName) { return Mockito.mockingDetails(lwM2mTransportServerHelperTest) .getInvocations().stream() .filter(invocation -> @@ -256,5 +289,4 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg ) .count(); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java index a1ad762aaa..56de08e66d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java @@ -58,7 +58,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -81,7 +80,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]"; @@ -100,7 +98,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_ObserveResourceNotReadableIsNull() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]"; @@ -120,7 +117,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]"; @@ -133,7 +129,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } /** - * Previous -> "3/0/9", "19/0/2", "19/1/0", "19/0/0", All only SingleObservation; + * Previous -> "3/0/9" SingleObservation; * if at least one of the resource objectIds (Composite) in SingleObservation or CompositeObservation is already registered - return BAD REQUEST * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]} * @throws Exception @@ -145,13 +141,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); String actualValues = rpcActualResultReadAll.get("value").asText(); - String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); - // Send Observe composite with "/3/0/9" + assertTrue(actualValues.contains("[]")); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -167,9 +158,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); actualValues = rpcActualResultReadAll.get("value").asText(); assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); } /** * Previous -> ["5/0/7", "5/0/5", "5/0/3"], CompositeObservation * @@ -206,12 +194,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); actualValues = rpcActualResultReadAll.get("value").asText(); - String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); assertTrue(actualValues.contains("CompositeObservation:")); assertTrue(actualValues.contains(expectedId5_0_7)); assertTrue(actualValues.contains(expectedId5_0_5)); @@ -224,8 +206,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -248,8 +228,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -274,6 +252,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeWithKeyName_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception { + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -292,8 +271,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveReadAll_AfterbserveCancelAllAndCompositeObservation_Result_CONTENT_Value_CompositeObservation_Only() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -323,7 +300,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCancelAllThenObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_1() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; @@ -339,7 +315,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("1", rpcActualResult.get("value").asText()); - assertEquals(0, (Object) getCntObserveAll(deviceId)); + assertEquals(0, (Object) getCntObserveAll(lwM2MTestClient.getDeviceIdStr())); } /** @@ -349,7 +325,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeFiveResources_Result_CONTENT_CancelObserveComposite_TwoAnyResource_Result_BadRequest() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite five String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; @@ -360,7 +335,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - awaitObserveReadAll(1, deviceId); + awaitObserveReadAll(1, lwM2MTestClient.getDeviceIdStr()); // ObserveCompositeCancel two expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; @@ -377,7 +352,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; @@ -412,17 +386,25 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String idVer_19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; String id_19_0_2 = fromVersionedIdToObjectId(idVer_19_0_2); - // 1 - "ObserveReadAll": at least one update value of all resources we observe - after connection + // 1 - Verify after start String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); - ArrayNode rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); - assertEquals(rpcactualValues.size(), 4); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_3_0_9)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_1_0)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_2)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_0)); + String actualValues = rpcActualResultReadAll.get("value").asText(); + assertTrue(actualValues.contains("[]")); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_0_0); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_1_0); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_0_2); + + actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); + rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); + actualValues = rpcActualResultReadAll.get("value").asText(); + assertTrue(actualValues.contains("SingleObservation:" + id_3_0_9)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_1_0)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_0_2)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_0_0)); long initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); long initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9); long initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0); @@ -436,13 +418,13 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_2, idVer_19_0_2); // 2 - "ObserveReadAll": No update of all resources we are observing - after "ObserveReadCancelAll" - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); updateRegAtLeastOnceAfterAction(); actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); - rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); + String rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); + ArrayNode rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); assertEquals(rpcactualValues.size(), 0); // 2.1 - ObserveComposite: observeCancelAll verify" initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); @@ -497,17 +479,17 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } else { sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; } - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), sendRpcRequest, String.class, status().isOk()); } private String sendCompositeRPCByIds(String method, String paths) throws Exception { String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"ids\":" + paths + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendCompositeRPCByKeys(String method, String keys) throws Exception { String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), sendRpcRequest, String.class, status().isOk()); } private void updateAttrTelemetryAllAtLeastOnceAfterAction(long initialInvocationCount) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java index 86ab77c733..93f8f3d88b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java @@ -127,7 +127,7 @@ public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTe private String sendRPCreateById(String path, String value) throws Exception { String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java index 9eef0578c5..a2b10c34bb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java @@ -90,7 +90,7 @@ public class RpcLwm2mIntegrationDeleteTest extends AbstractRpcLwM2MIntegrationTe private String sendRPCDeleteById(String path) throws Exception { String setRpcRequest = "{\"method\": \"Delete\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index 9518010f6a..1c0b4102a1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -22,6 +22,8 @@ import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.test.context.event.annotation.BeforeTestClass; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; @@ -30,8 +32,10 @@ import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTes import java.util.Arrays; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -39,10 +43,16 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { + @BeforeEach + public void beforeTest () throws Exception { + testInit(); + } + /** * DiscoverAll * @@ -51,7 +61,7 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration @Test public void testDiscoverAll_Return_CONTENT_LinksAllObjectsAllInstancesOfClient() throws Exception { String setRpcRequest = "{\"method\":\"DiscoverAll\"}"; - String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); JsonNode rpcActualValue = JacksonUtil.toJsonNode(rpcActualResult.get("value").asText()); @@ -171,9 +181,20 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); } + @Test + public void testDiscoverRequestCannotTargetResourceInstance_Return_INTERNAL_SERVER_ERROR() throws Exception { + // ResourceInstanceId + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; + String actualResult = sendDiscover(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + String expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1"; + assertTrue(rpcActualResult.get("error").asText().contains(expected)); + } + private String sendDiscover(String path) throws Exception { String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String convertObjectIdToVerId(String path, String ver) { @@ -190,4 +211,10 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration return null; } } + + public void testInit() throws Exception { + await("Update Registration at-least-once after start") + .atMost(50, TimeUnit.SECONDS) + .until(() -> countUpdateReg() > 0); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java index d7807ee24a..ebbfec1a10 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; @@ -32,68 +32,22 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { /** - * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: - * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} + * Class Attributes + * - dim (0-65535) Integer: Multiple-Instance Resource; R, Number of instances existing for a Multiple-Instance Resource + * Class Attributes + * - pmin (def = 0(sec)) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource + * - pmax (def = -- ) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource + * - Greater Than gt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource + * - Less Than lt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource + * - Step st (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource */ - @Test - public void testWriteAttributesResourceWithParametersByResourceInstanceId_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; - String expectedValue = "{\"pmax\":100, \"pmin\":10}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute pmax can be used for only Resource/Object Instance/Object."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - /** - * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: - * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} - */ - @Test - public void testWriteAttributeResourceDimWithParametersByResourceId_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; - String expectedValue = "{\"dim\":3}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute dim is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - - @Test - public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectIdVer_3; - String expectedValue = "{\"ver\":1.3}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - @Test - public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_1; - String actualResult = sendRPCReadById(expectedPath); - String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } /** + * Class Attributes + * Object Version ver Object + * Provide the version of the associated Object. + * "ver" only for objectId * Class Attributes * Dimension dim Integer [0:255] * Number of instances existing for a Multiple-Instance Resource @@ -105,145 +59,116 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL * Integer * 0..7 * WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3 - * "ver" only for objectId - */ - @Test - public void testReadDIM_3_0_6_Only_R () throws Exception { - String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; - String actualResult = sendDiscover(path); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expected = ";dim=3"; - assertTrue(rpcActualResult.get("value").asText().equals(expected)); - - } - - - /** - * Class Attributes - * Object Version ver Object - * Provide the version of the associated Object. - * "ver" only for objectId */ @Test - public void testReadVer () throws Exception { + public void testReadDIM_3_0_6_Only_R() throws Exception { String path = objectIdVer_3; String actualResult = sendDiscover(path); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expected = ";ver=1.2"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=1"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); } /** * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: * {"result":"CHANGED"} + * result changed: + * */ @Test public void testWriteAttributesResourceWithParametersById_Result_CHANGED() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - // result changed + // result changed actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expected = ";pmax=100;pmin=10"; + String expected = ";pmax=100;pmin=10;dim=3"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); } - /** - * Class Attributes - * Minimum/Maximum Period pmin/pmax - * Notes: The Minimum Period Attribute: - * -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account. - * Notes: The Maximum Period Attribute: - * -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied. - * Greater Than gt Resource - * Less Than lt Resource - * Step st Resource - * - * Object Id = 1 - * Default Minimum Period Id = 2 300 or 0 - * Default Maximum Period Id = 3 6000 or "-" - * ;pmax=65, , <3/0/2>, , , - * <3/0/6>;dim=8,<3/0/7>;gt=50;lt=42.2;st=0.5,<3/0/8>;... - */ @Test - public void testWriteAttributesPeriodLtGt () throws Exception { + public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_3; + String expectedValue = "{\"ver\":1.3}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + @Test + public void testWriteAttributesObjectInstanceResourcePeriodLtGt_Return_CHANGED() throws Exception { String expectedPath = objectInstanceIdVer_3; - String expectedValue = "{\"pmax\":60}"; + String expectedValue = "{\"pmax\":65, \"pmin\":5}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - expectedPath = objectInstanceIdVer_3; - expectedValue = "{\"pmax\":65}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; - expectedValue ="{\"gt\":50, \"lt\":42.2, \"st\":0.5}"; + String expectedValueStr = "gt=50;lt=42.2;st=0.5"; + JsonUtils.parse("{" + expectedValueStr + "}").toString(); + expectedValue = JsonUtils.parse("{" + expectedValueStr + "}").toString(); actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - // ObjectId + // ObjectId expectedPath = objectIdVer_3; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // String expected = ";ver=1.2,;pmax=60,,,,,;dim=3,;st=0.5;lt=42.2;gt=50.0,,,,;dim=1,,,,,,,,,"; - String expected = ";ver=1.2,;pmax=65"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ObjectInstanceId + String actualValue = rpcActualResult.get("value").asText(); + String expected = ";ver=1.2,;pmax=65;pmin=5"; + assertTrue(actualValue.contains(expected)); + expected = ";dim=3"; + assertTrue(actualValue.contains(expected)); + expected = ";" + expectedValueStr + ";dim=3"; + assertTrue(actualValue.contains(expected)); + // ObjectInstanceId expectedPath = objectInstanceIdVer_3; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";pmax=65"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ResourceId + actualValue = rpcActualResult.get("value").asText(); + expected = ";pmax=65;pmin=5"; + assertTrue(actualValue.contains(expected)); + expected = ";" + expectedValueStr + ";dim=3"; + assertTrue(actualValue.contains(expected)); + // ResourceId expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";dim=3"; + expected = ";dim=3,,,"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";st=0.5;lt=42.2;gt=50.0"; + expected = ";" + expectedValueStr; assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ResourceInstanceId - expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6+ "/1"; - actualResult = sendDiscover(expectedPath); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); - expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1"; - assertTrue(rpcActualResult.get("error").asText().contains(expected)); } private String sendRPCExecuteWithValueById(String path, String value) throws Exception { String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); - } - - private String sendRPCReadById(String path) throws Exception { - String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendDiscover(String path) throws Exception { String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java index d31dde1728..5aeb0a60b3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java @@ -174,12 +174,12 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT private String sendRPCExecuteById(String path) throws Exception { String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCExecuteWithValueById(String path, Object value) throws Exception { String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index a665f7f53c..4adf1ec000 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -20,14 +20,12 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; +import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; -import java.util.Optional; - import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -45,13 +43,15 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr @Slf4j public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest { + @Before + public void setupObserveTest() throws Exception { + awaitObserveReadAll(4,lwM2MTestClient.getDeviceIdStr()); + } + + @Test public void testObserveReadAll_Count_4_CancelAll_Count_0_Ok() throws Exception { - String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); - assertEquals(4, actualValuesReadAll.split(",").length); - sendObserveCancelAllWithAwait(deviceId); - actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); - assertEquals("[]", actualValuesReadAll); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); } /** @@ -61,7 +61,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { long initSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); updateRegAtLeastOnceAfterAction(); long lastSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); @@ -74,7 +74,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); String idVer_3_0 = objectInstanceIdVer_3; sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); @@ -89,7 +89,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); String idVer_3_0 = objectInstanceIdVer_3; sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); @@ -199,7 +199,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserves_OverlappedPaths_FirstResource_SecondObjectOrInstance() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); // "19/0/0" sendRpcObserveOkWithResultValue("Observe", idVer_19_0_0); // PreviousObservation "19/0/0" change to CurrentObservation "19" - object @@ -247,7 +247,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); String actualValuesReadAll = sendRpcObserveReadAllWithResult(idVer_3_0_9); assertEquals(1, actualValuesReadAll.split(",").length); @@ -265,7 +265,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); String actualValuesReadAll = sendRpcObserveReadAllWithResult(objectIdVer_3); assertEquals(1, actualValuesReadAll.split(",").length); @@ -292,7 +292,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); sendRpcObserveWithWithTwoResource(idVer_3_0_0, idVer_3_0_9); String rpcActualResul = sendRpcObserveOkWithResultValue("ObserveReadAll", null); assertEquals(2, rpcActualResul.split(",").length); @@ -320,15 +320,13 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { - sendObserveCancelAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); - int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) - .updatedReg(Mockito.any(Registration.class)); + awaitUpdateReg(3); sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); - cntUpdate = 10; + int cntUpdate = 10; verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null)); } @@ -344,11 +342,5 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); return rpcActualResult.get("value").asText(); } - - private void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { - String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); - assertTrue(rpcActualResult.contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java new file mode 100644 index 0000000000..55a69353d3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java @@ -0,0 +1,98 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; +import java.util.concurrent.atomic.AtomicReference; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + +@Slf4j +public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * Read {"id":"/3303/12/5700"} + * Trigger a Send operation from the client with multiple values for the same resource as a payload + * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] + * 2 values for the resource /3303/12/5700 should be stored with: + * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 + * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 + * @throws Exception + */ + @Test + public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { + // init test + int cntValues = 2; + int resourceId = 5700; + String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; + sendRPCById(expectedIdVer); + + // verify time start/end send CollectedValue; + await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 + && RESOURCE_ID_3303_12_5700_TS_1 > 0); + + // verify result read: verify count value: 1-2: send CollectedValue; + AtomicReference actualValues = new AtomicReference<>(); + await().atMost(40, SECONDS).until(() -> { + actualValues.set(doGetAsync( + "/api/plugins/telemetry/DEVICE/" + lwM2MTestClient.getDeviceIdStr() + "/values/timeseries?keys=" + + RESOURCE_ID_NAME_3303_12_5700 + + "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&interval=0&limit=100&useStrictDataTypes=false", + ObjectNode.class)); + return actualValues.get() != null && actualValues.get().size() > 0 + && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); + }); + } + + private boolean verifyTs(AtomicReference actualValues) { + String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); + String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); + ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); + long actualTS0 = 0; + long actualTS1 = 0; + for (JsonNode tsNode : actual) { + if (tsNode.get("value").asText().equals(expectedVal_0)) { + actualTS0 = tsNode.get("ts").asLong(); + } else if (tsNode.get("value").asText().equals(expectedVal_1)) { + actualTS1 = tsNode.get("ts").asLong(); + } + } + return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 + && actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 + && (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + } + + private String sendRPCById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index 1ab4893ec4..afa35735c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -15,23 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.rpc.sql; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.map.HashedMap; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,24 +31,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; @Slf4j public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { @@ -228,59 +213,6 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest assertTrue(actualValues.contains(expected19_1_0)); } - - /** - * Read {"id":"/3303/12/5700"} - * Trigger a Send operation from the client with multiple values for the same resource as a payload - * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] - * 2 values for the resource /3303/12/5700 should be stored with: - * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 - * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 - * @throws Exception - */ - @Test - public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { - // init test - long startTs = Instant.now().toEpochMilli(); - int cntValues = 4; - int resourceId = 5700; - String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; - sendRPCById(expectedIdVer); - // verify result read: verify count value: 1-2: send CollectedValue; 3 - response for read; - long endTs = Instant.now().toEpochMilli() + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS * 4; - String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); - String expectedVal_2 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); - AtomicReference actualValues = new AtomicReference<>(); - await().atMost(40, SECONDS).until(() -> { - actualValues.set(doGetAsync( - "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" - + RESOURCE_ID_NAME_3303_12_5700 - + "&startTs=" + startTs - + "&endTs=" + endTs - + "&interval=0&limit=100&useStrictDataTypes=false", - ObjectNode.class)); - // verify cntValues - return actualValues.get() != null && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() == cntValues; - }); - // verify ts - ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); - Map keyTsMaps = new HashedMap(); - for (JsonNode tsNode: actual) { - if (tsNode.get("value").asText().equals(expectedVal_1) || tsNode.get("value").asText().equals(expectedVal_2)) { - keyTsMaps.put(tsNode.get("value").asText(), tsNode.get("ts").asLong()); - } - } - assertTrue(keyTsMaps.size() == 2); - long actualTS0 = keyTsMaps.get(expectedVal_1).longValue(); - long actualTS1 = keyTsMaps.get(expectedVal_2).longValue(); - assertTrue(actualTS0 > 0); - assertTrue(actualTS1 > 0); - assertTrue(actualTS1 > actualTS0); - assertTrue((actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - assertTrue(actualTS0 <= RESOURCE_ID_3303_12_5700_TS_0); - assertTrue(actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1); - } - /** * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]} */ @@ -300,21 +232,21 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest private String sendRPCById(String path) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCByKey(String key) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendCompositeRPCByIds(String paths) throws Exception { String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"ids\":" + paths + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendCompositeRPCByKeys(String keys) throws Exception { String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"keys\":" + keys + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteCborTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteCborTest.java index 2850d5b8cd..f3f8e0b3be 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteCborTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteCborTest.java @@ -88,11 +88,11 @@ public class RpcLwm2mIntegrationWriteCborTest extends AbstractRpcLwM2MIntegratio private String sendRPCWriteObjectById(String method, String path, Object value) throws Exception { String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCReadById(String id) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + id + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java index f99826780e..dce5233984 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import static org.junit.Assert.assertEquals; @@ -76,7 +77,157 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue + ", type=STRING]"; assertTrue(actualValues.contains(expected)); } - + @Test + public void testWriteReplaceValueMultipleResource_Result_CHANGED_Multi_Instance_Resource_must_One() throws Exception { + int resourceInstanceId0 = 0; + String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; + // base64/String + String expectedValue = "QUJDREVGRw"; + String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + byte[] expectedValue0 = TbUtils.base64ToBytes(expectedValue); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // base64/String + expectedValue = "ABCDEFG"; + actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expectedValue0 = TbUtils.base64ToBytes(expectedValue); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // hexDecimal/String + expectedValue = "01ABCDEF"; + actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Integer + Integer expectedIntegerValue = 1234566; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedIntegerValue = Integer.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedIntegerValue = Integer.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Long + Long expectedLongValue = 4406483977L; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedLongValue = Long.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedLongValue = Long.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Float to byte[]: byte[] bytes = ByteBuffer.allocate(4).putFloat(((Float) value).floatValue()).array(); + // Float from byte[]: float f = ByteBuffer.wrap(bytes).getFloat(); + Float expectedFloatValue = 8.02f; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedFloatValue = Float.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedFloatValue = Float.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Double to byte[]: byte[] bytes = ByteBuffer.allocate(8).putDouble(((Double) value).doubleValue()).array(); + // Double from byte[]: double d = ByteBuffer.wrap(bytes).getDouble(); + Double expectedDoubleValue = 1022.5906d; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedDoubleValue = Double.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedDoubleValue = Double.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + } /** * id @@ -88,9 +239,9 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; int resourceInstanceId0 = 0; int resourceInstanceId15 = 15; - String expectedValue0 = "0000ad45675600"; - String expectedValue15 = "1525ad45675600cdef"; - String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":\"" + expectedValue15 + "\"}"; + String expectedValue0 = "1525ad45675600cdef"; + Integer expectedValue15 = Integer.MAX_VALUE; + String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":" + expectedValue15 + "}"; String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); @@ -99,12 +250,12 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath15); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + expectedValue15.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + Integer.toHexString(expectedValue15).length()/2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -130,7 +281,7 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertFalse(actualValues.contains(expected)); } @@ -193,16 +344,16 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); String expectedPath0 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; - String expectedPath25 =expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25; + String expectedPath25 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25; actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -232,13 +383,14 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; - assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; @@ -270,12 +422,12 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -301,7 +453,7 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath19_1_0_2); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + RESOURCE_INSTANCE_ID_2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + RESOURCE_INSTANCE_ID_2 + ", value=" + expectedValue19_1_0_2.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -346,31 +498,31 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes private String sendRPCWriteStringById(String method, String path, String value) throws Exception { String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\" }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCWriteObjectById(String method, String path, Object value) throws Exception { String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCReadById(String id) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + id + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCWriteByKey(String method, String key, String value) throws Exception { String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"key\": \"" + key + "\", \"value\": \"" + value + "\" }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendRPCReadByKey(String key) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } private String sendCompositeRPC(String nodes) throws Exception { String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index e787d26265..eaade683d8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -25,6 +25,7 @@ import org.junit.Assert; import org.springframework.test.web.servlet.MvcResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MClientSecurityCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; @@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBo import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.PSKLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -203,9 +205,9 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isAwaitObserveReadAll, LwM2MClientState finishState, boolean isStartLw) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, endpoint); - createNewClient(security, securityBs, true, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); + createNewClient(security, securityBs, true, endpoint, device.getId().getId().toString()); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { awaitObserveReadAll(0, device.getId().getId().toString()); @@ -216,6 +218,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M log.warn("basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); }); + + awaitUpdateReg(1); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -248,10 +252,10 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M Set expectedStatusesLwm2m, Set expectedStatusesBs) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, securityBs, true, endpoint); + createNewClient(security, securityBs, true, endpoint, deviceIdStr); lwM2MTestClient.start(true); awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) @@ -260,6 +264,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M log.warn("basicTest First Connection started -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates()); return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); }); + + awaitUpdateReg(1); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -286,6 +292,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M log.warn("basicTestConnection started -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates()); return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); }); + + awaitUpdateReg(1); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -390,12 +398,12 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M protected void initDeviceCredentialsNoSek() { - clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_" + randomSuffix.nextInt(100); + clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_" + randomSuffix.nextInt(1000); security = SECURITY_NO_SEC; deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); } protected void initDeviceCredentialsPsk() { - int suf = randomSuffix.nextInt(10); + int suf = randomSuffix.nextInt(1000); clientEndpoint = CLIENT_ENDPOINT_PSK + "_" + suf; String identity = CLIENT_PSK_IDENTITY + "_" + suf; clientCredentials = new PSKClientCredential(); @@ -446,10 +454,10 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return bootstrapCredentials; } - protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint) throws Exception { + protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint, DeviceProfileId deviceProfileId) throws Exception { Device device = new Device(); device.setName(endpoint); - device.setDeviceProfileId(deviceProfile.getId()); + device.setDeviceProfileId(deviceProfileId); device.setTenantId(tenantId); device = doPost("/api/device", device, Device.class); Assert.assertNotNull(device); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java index 291149b7e8..5b3457fe92 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpoint; import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; import org.junit.Assert; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; @@ -49,11 +50,12 @@ public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends protected void basicTestConnectionDtlsCidLength(Integer clientDtlsCidLength, Integer serverDtlsCidLength) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, clientEndpoint); - device.getId().getId().toString(); - createNewClient(security, null, true, clientEndpoint, clientDtlsCidLength); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, clientEndpoint, deviceProfile.getId()); + createNewClient(security, null, true, clientEndpoint, clientDtlsCidLength, device.getId().getId().toString()); lwM2MTestClient.start(true); + + awaitUpdateReg(1); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java index 9e9a38925c..c9161bbf0a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java @@ -19,21 +19,28 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.peer.IpPeer; import org.eclipse.leshan.core.peer.LwM2mPeer; import org.eclipse.leshan.core.peer.SocketIdentity; +import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.junit.Assert; +import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Collection; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; @@ -59,20 +66,27 @@ public abstract class AbstractLwM2MIntegrationDiffPortTest extends AbstractSecur return invocation.callRealMethod(); }).when(registrationStoreTest).updateRegistration(any(RegistrationUpdate.class)); - createDeviceProfile(transportConfiguration); - createDevice(deviceCredentials, clientEndpoint); - createNewClient(security, null, true, clientEndpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, clientEndpoint, deviceProfile.getId()); + createNewClient(security, null, true, clientEndpoint, device.getId().getId().toString()); lwM2MTestClient.start(true); + + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(1)) + .onRegistered(Mockito.any(Registration.class), Mockito.any()); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED)); Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccess)); + awaitUpdateReg(1); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS)); Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccessUpdate)); + + long cntBefore = countUpdateReg(); + awaitUpdateReg((int) (cntBefore + 1)); } private RegistrationUpdate registrationUpdateNewPort (RegistrationUpdate update, int portValueChange) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index e5c9cbf469..ac1c0866de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -76,9 +77,9 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes clientCredentials.setIdentity(identity); clientCredentials.setKey(keyPsk); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); - createDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Key must be HexDec format: 32, 64, 128 characters!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index c33d4b5059..af0be89feb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -33,6 +34,7 @@ import static org.eclipse.leshan.client.object.Security.rpk; import static org.eclipse.leshan.client.object.Security.rpkBootstrap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.RPK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -75,10 +77,11 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Hex.encodeHexString(certificate.getPublicKey().getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "LwM2M client RPK key must be in standard [RFC7250] and support only EC algorithm and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); @@ -92,10 +95,10 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, true); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Bootstrap server client RPK secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 072dea9a6e..743c007fdb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -33,6 +34,7 @@ import static org.eclipse.leshan.client.object.Security.x509; import static org.eclipse.leshan.client.object.Security.x509Bootstrap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -76,10 +78,10 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setCert(Hex.encodeHexString(certificate.getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "LwM2M client X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); @@ -93,10 +95,10 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, true); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Bootstrap server client X509 secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index 37cf196410..cde0d4421e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -79,7 +79,7 @@ public interface ResourceService extends EntityDaoService { void processResourcesForImport(WidgetTypeDetails widgetTypeDetails, Map importedResources); - TbResource updateSystemResource(ResourceType resourceType, String resourceKey, String data); + TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, String data); String checkSystemResourcesUsage(String content, ResourceType... usedResourceTypes); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 8f22812cfc..586ae50f5d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -97,7 +97,7 @@ public interface UserService extends EntityDaoService { int increaseFailedLoginAttempts(TenantId tenantId, UserId userId); - void setLastLoginTs(TenantId tenantId, UserId userId); + void updateLastLoginTs(TenantId tenantId, UserId userId); void saveMobileSession(TenantId tenantId, UserId userId, String mobileToken, MobileSessionInfo sessionInfo); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java index 1104ae2949..4a882795db 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java @@ -40,6 +40,8 @@ public class UserCredentials extends BaseDataWithAdditionalInfo value = convertMultiResourceValuesFromRpcBody(request.getValue(), resourceModelWrite.type, request.getObjectId()); + Object valueForMultiResource = request.getValue(); + if (resultIds.isResourceInstance()) { + String resourceInstance = "{" + resultIds.getResourceInstanceId() + "=" + request.getValue() + "}"; + valueForMultiResource = JsonParser.parseString(resourceInstance); + } + Map value = convertMultiResourceValuesFromRpcBody(valueForMultiResource, resourceModelWrite.type, request.getObjectId()); downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), value, resourceModelWrite.type); } catch (Exception e) { @@ -707,7 +713,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); if (pathIds.isResourceInstance() || pathIds.isResource()) { ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) { + if (resourceModel != null && !resourceModel.multiple) { ContentFormat[] desiredFormats; if (OBJLNK.equals(resourceModel.type)) { desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 4655a27316..ca2a82968b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -277,17 +277,6 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); - LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); - if (path.isResource()) { - ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - if (resourceModel != null && resourceModel.multiple) { - try { - Map value = convertMultiResourceValuesFromRpcBody(requestBody.getValue(), resourceModel.type, versionedId); - requestBody.setValue(value); - } catch (Exception e) { - } - } - } TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(clientContext.getRequestTimeout(client)).build(); @@ -330,7 +319,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { if (versionedId == null) { if (path.isResourceInstance()) { - setValueToCompositeNodes(client, newNodes, nodes, key, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, key, value); } else if (path.isResource()) { validateResource(client, newNodes, nodes, key , value); } else if (path.isObjectInstance() && value instanceof Map) { @@ -342,7 +331,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes)); } } else { - setValueToCompositeNodes(client, newNodes, nodes, versionedId, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, versionedId, value); } }); return newNodes; @@ -351,10 +340,10 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void validateResource(LwM2mClient client, Map newNodes, Map nodes, String resourceId , Object value) { if (value instanceof Map) { ((Map) value).forEach((k, v) -> { - setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v.toString()); + setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v); }); } else { - setValueToCompositeNodes(client, newNodes, nodes, resourceId, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, resourceId, value); } } @@ -368,7 +357,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } } - private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , String value) { + private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , Object value) { // validate value. Must be only primitive, not JsonObject or JsonArray try { JsonElement element = JsonUtils.parse(value); @@ -378,7 +367,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } // convert value from JsonPrimitive() to resource/ResourceInstance type ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - Object newValue = convertValueByTypeResource(value, resourceModel.type, versionedId); + Object newValue = convertValueByTypeResource(element, resourceModel.type, versionedId); // add new value after convert newNodes.put(fromVersionedIdToObjectId(versionedId), newValue); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 6b90ae21b6..66f68f0a05 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.utils; import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.core.model.LwM2mModel; @@ -27,7 +28,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.util.Hex; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.ota.OtaPackageKey; -import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -62,6 +61,7 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.common.transport.util.JsonUtils.convertToJsonObject; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; @@ -180,9 +180,11 @@ public class LwM2MTransportUtil { */ public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { switch (value.getClass().getSimpleName()) { + case "Float": case "Double": return FLOAT; case "Integer": + case "Long": return INTEGER; case "String": return STRING; @@ -199,6 +201,30 @@ public class LwM2MTransportUtil { } } + public static Object getJsonPrimitiveValue(JsonPrimitive value) { + if(value.isString()) { + return value.getAsString(); + } else if (value.isNumber()){ + try { + return Integer.valueOf(value.toString()); + } catch (NumberFormatException i) { + try { + return Long.valueOf(value.toString()); + } catch (NumberFormatException l){ + if (value.getAsFloat() >= Float.MIN_VALUE && value.getAsFloat() <= Float.MAX_VALUE) { + return value.getAsFloat(); + } else { + return value.getAsDouble(); + } + } + } + } else if (value.isBoolean()){ + return value.getAsBoolean(); + } else { + return null; + } + } + public static void validateVersionedId(LwM2mClient client, HasVersionedId request) { String msgExceptionStr = ""; if (request.getObjectId() == null) { @@ -212,22 +238,29 @@ public class LwM2MTransportUtil { } public static Map convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception { - String valueJsonStr = JacksonUtil.toString(value); - JsonElement element = JsonUtils.parse(valueJsonStr); - return convertMultiResourceValuesFromJson(element, type, versionedId); + if (value instanceof JsonElement) { + return convertMultiResourceValuesFromJson((JsonElement) value, type, versionedId); + } else if (value instanceof Map) { + JsonElement valueConvert = convertToJsonObject((Map) value); + return convertMultiResourceValuesFromJson(valueConvert, type, versionedId); + } else { + return null; + } } public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { Map newValues = new HashMap<>(); newValProto.getAsJsonObject().entrySet().forEach((obj) -> { - newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource(obj.getValue().getAsString(), type, versionedId)); + Object valueByTypeResource = convertValueByTypeResource(obj.getValue(), type, versionedId); + newValues.put(Integer.valueOf(obj.getKey()), valueByTypeResource); }); return newValues; } - public static Object convertValueByTypeResource(String value, ResourceModel.Type type, String versionedId) { - return LwM2mValueConverterImpl.getInstance().convertValue(value, - STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); + public static Object convertValueByTypeResource(Object value, ResourceModel.Type type, String versionedId) { + Object valueCurrent = getJsonPrimitiveValue((JsonPrimitive) value); + return LwM2mValueConverterImpl.getInstance().convertValue(valueCurrent, + equalsResourceTypeGetSimpleName(valueCurrent), type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 3125ae63f5..a584211605 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.util.Hex; import org.thingsboard.server.common.data.StringUtils; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Base64; @@ -165,7 +166,19 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { } break; case OPAQUE: - if (currentType == Type.STRING) { + if (currentType == Type.INTEGER) { + if (value instanceof Integer) { + return ByteBuffer.allocate(4).putInt((Integer) value).array(); + } else { + return ByteBuffer.allocate(8).putLong((Long) value).array(); + } + } else if (currentType == Type.FLOAT) { + if (value instanceof Float) { + return ByteBuffer.allocate(4).putFloat((Float) value).array(); + } else { + return ByteBuffer.allocate(8).putDouble((Double) value).array(); + } + } else if (currentType == Type.STRING) { /** let's assume we received an hexadecimal string */ log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value); try { @@ -178,6 +191,8 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { value, resourcePath); } } + } else if (currentType == Type.BOOLEAN) { + return new byte[] {(byte)((boolean)value ? 1 : 0)}; } break; case OBJLNK: diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 8e48061db6..0a097d938b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java index 1f273e6f04..ed0b425c76 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java @@ -18,9 +18,11 @@ package org.thingsboard.server.common.transport.util; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import java.util.List; +import java.util.Map; public class JsonUtils { @@ -47,9 +49,30 @@ public class JsonUtils { } return json; } - - public static JsonElement parse(String params) { - return JsonParser.parseString(params); + public static JsonElement parse(Object value) { + if (value instanceof Integer) { + return new JsonPrimitive((Integer) value); + } else if (value instanceof Long) { + return new JsonPrimitive((Long) value); + } else if (value instanceof String) { + return JsonParser.parseString((String) value); + } else if (value instanceof Boolean) { + return new JsonPrimitive((Boolean) value); + } else if (value instanceof Double) { + return new JsonPrimitive((Double) value); + } else if (value instanceof Float) { + return new JsonPrimitive((Float) value); + } else { + throw new IllegalArgumentException("Unsupported type: " + value.getClass().getSimpleName()); + } } + public static JsonObject convertToJsonObject(Map map) { + JsonObject jsonObject = new JsonObject(); + for (Map.Entry entry : map.entrySet()) { + jsonObject.add(entry.getKey(), parse(entry.getValue())); + } + + return jsonObject; + } } diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java index 1f434c1447..50d1a00b09 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.sync.vc.BranchInfo; import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.util.CollectionsUtil; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -184,9 +185,9 @@ public class GitRepository { } } - public void fetch() throws GitAPIException { + public boolean fetch() throws GitAPIException { if (settings.isLocalOnly()) { - return; + return false; } log.debug("Executing fetch [{}]", settings.getRepositoryUri()); FetchResult result = execute(git.fetch() @@ -195,6 +196,7 @@ public class GitRepository { if (head != null) { this.headId = head.getObjectId(); } + return CollectionsUtil.isNotEmpty(result.getTrackingRefUpdates()); } public void deleteLocalBranchIfExists(String branch) throws GitAPIException { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index e958313d67..cce114f361 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.core.security.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index b920160cc0..f85cd4bac4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -82,7 +82,8 @@ public class ModelConstants { public static final String USER_CREDENTIALS_ACTIVATE_TOKEN_EXP_TIME_PROPERTY = "activate_token_exp_time"; public static final String USER_CREDENTIALS_RESET_TOKEN_PROPERTY = "reset_token"; public static final String USER_CREDENTIALS_RESET_TOKEN_EXP_TIME_PROPERTY = "reset_token_exp_time"; - public static final String USER_CREDENTIALS_ADDITIONAL_PROPERTY = "additional_info"; + public static final String USER_CREDENTIALS_LAST_LOGIN_TS_PROPERTY = "last_login_ts"; + public static final String USER_CREDENTIALS_FAILED_LOGIN_ATTEMPTS_PROPERTY = "failed_login_attempts"; /** * User settings constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java index edd1536a04..7ac871932f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java @@ -60,18 +60,21 @@ public final class UserCredentialsEntity extends BaseSqlEntity private Long resetTokenExpTime; @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY) + @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.USER_CREDENTIALS_LAST_LOGIN_TS_PROPERTY) + private Long lastLoginTs; + + @Column(name = ModelConstants.USER_CREDENTIALS_FAILED_LOGIN_ATTEMPTS_PROPERTY) + private Integer failedLoginAttempts; + public UserCredentialsEntity() { super(); } public UserCredentialsEntity(UserCredentials userCredentials) { - if (userCredentials.getId() != null) { - this.setUuid(userCredentials.getId().getId()); - } - this.setCreatedTime(userCredentials.getCreatedTime()); + super(userCredentials); if (userCredentials.getUserId() != null) { this.userId = userCredentials.getUserId().getId(); } @@ -82,6 +85,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity this.resetToken = userCredentials.getResetToken(); this.resetTokenExpTime = userCredentials.getResetTokenExpTime(); this.additionalInfo = userCredentials.getAdditionalInfo(); + this.lastLoginTs = userCredentials.getLastLoginTs(); + this.failedLoginAttempts = userCredentials.getFailedLoginAttempts(); } @Override @@ -98,6 +103,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity userCredentials.setResetToken(resetToken); userCredentials.setResetTokenExpTime(resetTokenExpTime); userCredentials.setAdditionalInfo(additionalInfo); + userCredentials.setLastLoginTs(lastLoginTs); + userCredentials.setFailedLoginAttempts(failedLoginAttempts); return userCredentials; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 2c76f9bb36..9ae1dd2298 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -343,7 +343,7 @@ public class BaseResourceService extends AbstractCachedEntityService { void removeByUserId(TenantId tenantId, UserId userId); + void setLastLoginTs(TenantId tenantId, UserId userId, long lastLoginTs); + + int incrementFailedLoginAttempts(TenantId tenantId, UserId userId); + + void setFailedLoginAttempts(TenantId tenantId, UserId userId, int failedLoginAttempts); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index ba0d3d69f3..dc758b6b5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -17,9 +17,6 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.BooleanNode; -import com.fasterxml.jackson.databind.node.IntNode; -import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -87,15 +84,10 @@ public class UserServiceImpl extends AbstractCachedEntityService INCORRECT_USER_ID + id); UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, userId.getId()); userCredentials.setEnabled(enabled); - saveUserCredentials(tenantId, userCredentials); - - User user = findUserById(tenantId, userId); - user.setAdditionalInfoField(USER_CREDENTIALS_ENABLED, BooleanNode.valueOf(enabled)); if (enabled) { - resetFailedLoginAttempts(user); + userCredentials.setFailedLoginAttempts(0); } - saveUser(tenantId, user); + saveUserCredentials(tenantId, userCredentials); } - @Override public void resetFailedLoginAttempts(TenantId tenantId, UserId userId) { - log.trace("Executing onUserLoginSuccessful [{}]", userId); - User user = findUserById(tenantId, userId); - resetFailedLoginAttempts(user); - saveUser(tenantId, user); - } - - private void resetFailedLoginAttempts(User user) { - user.setAdditionalInfoField(FAILED_LOGIN_ATTEMPTS, IntNode.valueOf(0)); + log.trace("Executing resetFailedLoginAttempts [{}]", userId); + userCredentialsDao.setFailedLoginAttempts(tenantId, userId, 0); } @Override - public void setLastLoginTs(TenantId tenantId, UserId userId) { - User user = findUserById(tenantId, userId); - user.setAdditionalInfoField(LAST_LOGIN_TS, new LongNode(System.currentTimeMillis())); - saveUser(tenantId, user); + public void updateLastLoginTs(TenantId tenantId, UserId userId) { + userCredentialsDao.setLastLoginTs(tenantId, userId, System.currentTimeMillis()); } @Override @@ -502,18 +482,8 @@ public class UserServiceImpl extends AbstractCachedEntityService1.7.0 4.4.0 2.2.14 - 3.11.0 - 2.0.0-M14 + 3.12.1 + 2.0.0-M15 2.10.1 2.3.32 2.0.1 diff --git a/ui-ngx/.eslintrc.json b/ui-ngx/.eslintrc.json index 598c8074a6..2d17064e40 100644 --- a/ui-ngx/.eslintrc.json +++ b/ui-ngx/.eslintrc.json @@ -61,9 +61,14 @@ "*.html" ], "extends": [ - "plugin:@angular-eslint/template/recommended" + "plugin:@angular-eslint/template/recommended", + "plugin:tailwindcss/recommended" ], - "rules": {} + "rules": { + "tailwindcss/no-custom-classname": "off", + "tailwindcss/migration-from-tailwind-2": "off", + "tailwindcss/enforces-negative-arbitrary-values": "off" + } } ] } diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 5f67ac948f..7a0049d160 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -103,7 +103,7 @@ "split.js": "^1.6.5", "systemjs": "6.15.1", "tinycolor2": "^1.6.0", - "tinymce": "~6.8.4", + "tinymce": "~6.8.5", "tooltipster": "^4.2.8", "ts-transformer-keys": "^0.4.4", "tslib": "^2.7.0", @@ -144,19 +144,23 @@ "@types/systemjs": "6.15.1", "@types/tinycolor2": "^1.4.6", "@types/tooltipster": "^0.0.35", - "@typescript-eslint/eslint-plugin": "^8.7.0", - "@typescript-eslint/parser": "^8.7.0", - "@typescript-eslint/utils": "^8.7.0", + "@typescript-eslint/eslint-plugin": "^8.11.0", + "@typescript-eslint/parser": "^8.11.0", + "@typescript-eslint/utils": "^8.11.0", + "autoprefixer": "^10.4.20", "compression-webpack-plugin": "^11.1.0", "directory-tree": "^3.5.2", - "eslint": "~8.57.1", + "eslint": "~9.13.0", "eslint-plugin-import": "latest", "eslint-plugin-jsdoc": "latest", "eslint-plugin-prefer-arrow": "latest", + "eslint-plugin-tailwindcss": "^3.17.5", "ngrx-store-freeze": "^0.2.4", "patch-package": "^8.0.0", + "postcss": "^8.4.47", "postinstall-prepare": "^2.0.0", "raw-loader": "^4.0.2", + "tailwindcss": "^3.4.13", "ts-node": "^10.9.2", "typescript": "~5.5.4", "webpack": "5.95.0" @@ -165,6 +169,6 @@ "@types/react": "18.3.10", "rc-virtual-list": "3.5.2", "ace-builds": "1.36.2", - "tinymce": "6.8.4" + "tinymce": "6.8.5" } } diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index a8e2ccbe02..43f3f14df4 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -35,7 +35,6 @@ import { TbMissingTranslationHandler } from './translate/missing-translate-handl import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DEFAULT_OPTIONS, MatDialogConfig, MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; import { WINDOW_PROVIDERS } from '@core/services/window.service'; import { HotkeyModule } from 'angular2-hotkeys'; @@ -44,7 +43,6 @@ import { TranslateDefaultLoader } from '@core/translate/translate-default-loader import { EntityConflictInterceptor } from '@core/interceptors/entity-conflict.interceptor'; @NgModule({ exports: [], imports: [CommonModule, - FlexLayoutModule.withConfig({ addFlexToParent: false }), MatDialogModule, MatButtonModule, MatSnackBarModule, diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index f0341410bc..235924f98f 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -636,7 +636,7 @@ export class DashboardUtilsService { targetLayout: DashboardLayoutId, widget: Widget, originalColumns?: number, - originalSize?: {sizeX: number; sizeY: number}, + originalSize?: WidgetSize, row?: number, column?: number, breakpoint = 'default'): void { @@ -661,8 +661,8 @@ export class DashboardUtilsService { mobileHeight: widget.config.mobileHeight, mobileHide: widget.config.mobileHide, desktopHide: widget.config.desktopHide, - preserveAspectRatio: widget.config.preserveAspectRatio, - resizable: widget.config.resizable + preserveAspectRatio: originalSize ? originalSize.preserveAspectRatio : widget.config.preserveAspectRatio, + resizable: originalSize ? originalSize.resizable : widget.config.resizable }; if (isUndefined(originalColumns)) { originalColumns = 24; @@ -1065,7 +1065,9 @@ export class DashboardUtilsService { const widgetLayout = layout.widgets[widget.id]; return { sizeX: widgetLayout.sizeX, - sizeY: widgetLayout.sizeY + sizeY: widgetLayout.sizeY, + preserveAspectRatio: widgetLayout.preserveAspectRatio, + resizable: widgetLayout.resizable }; } diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 40ac9c354b..cbf56dc44d 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -15,10 +15,11 @@ /// import { Component, Injectable, Type, ɵComponentDef, ɵNG_COMP_DEF } from '@angular/core'; -import { from, Observable, of } from 'rxjs'; +import { forkJoin, from, Observable, of } from 'rxjs'; import { CommonModule } from '@angular/common'; import { mergeMap } from 'rxjs/operators'; import { guid } from '@core/utils'; +import { getFlexLayoutModule } from '@shared/legacy/flex-layout.models'; @Injectable({ providedIn: 'root' @@ -34,9 +35,9 @@ export class DynamicComponentFactoryService { imports?: Type[], preserveWhitespaces?: boolean, styles?: string[]): Observable> { - return from(import('@angular/compiler')).pipe( - mergeMap(() => { - let componentImports: Type[] = [CommonModule]; + return forkJoin({flexLayoutModule: getFlexLayoutModule(), compiler: from(import('@angular/compiler'))}).pipe( + mergeMap((data) => { + let componentImports: Type[] = [CommonModule, data.flexLayoutModule]; if (imports) { componentImports = [...componentImports, ...imports]; } diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts index ee4c36b6f2..917d4e28bf 100644 --- a/ui-ngx/src/app/core/services/menu.models.ts +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -88,6 +88,7 @@ export enum MenuId { devices = 'devices', assets = 'assets', entity_views = 'entity_views', + gateways = 'gateways', profiles = 'profiles', device_profiles = 'device_profiles', asset_profiles = 'asset_profiles', @@ -508,6 +509,16 @@ export const menuSectionMap = new Map([ icon: 'view_quilt' } ], + [ + MenuId.gateways, + { + id: MenuId.gateways, + name: 'gateway.gateways', + type: 'link', + path: '/entities/gateways', + icon: 'tb_gateway' + } + ], [ MenuId.profiles, { @@ -725,7 +736,8 @@ const defaultUserMenuMap = new Map([ pages: [ {id: MenuId.devices}, {id: MenuId.assets}, - {id: MenuId.entity_views} + {id: MenuId.entity_views}, + {id: MenuId.gateways} ] }, { diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 8088cf0736..21bd11293e 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -27,7 +27,7 @@ import { ɵNgModuleDef } from '@angular/core'; import { DOCUMENT } from '@angular/common'; -import { Observable, ReplaySubject, throwError } from 'rxjs'; +import { forkJoin, from, Observable, ReplaySubject, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { IModulesMap } from '@modules/common/modules-map.models'; import { TbResourceId } from '@shared/models/id/tb-resource-id'; @@ -38,6 +38,7 @@ import { selectIsAuthenticated } from '@core/auth/auth.selectors'; import { AppState } from '@core/core.state'; import { map, tap } from 'rxjs/operators'; import { RequestConfig } from '@core/http/http-utils'; +import { getFlexLayoutModule } from '@app/shared/legacy/flex-layout.models'; export interface ModuleInfo { module: ɵNgModuleDef; @@ -187,26 +188,46 @@ export class ResourcesService { if (this.loadedModulesWithComponents[url]) { return this.loadedModulesWithComponents[url].asObservable(); } - modulesMap.init(); const meta = this.getMetaInfo(resourceId); const subject = new ReplaySubject(); this.loadedModulesWithComponents[url] = subject; - import('@angular/compiler').then( + + forkJoin( + [ + modulesMap.init(), + from(import('@angular/compiler')) + ] + ).subscribe( () => { // @ts-ignore System.import(url, undefined, meta).then( (module: any) => { try { const modulesWithComponents = this.extractModulesWithComponents(module); - if (modulesWithComponents.modules.length || modulesWithComponents.standaloneComponents.length) { - for (const module of modulesWithComponents.modules) { - createNgModule(module.module.type, this.injector); + this.patchModulesWithFlexLayout(modulesWithComponents).subscribe( + { + next: modules => { + if (modules.modules.length || modules.standaloneComponents.length) { + try { + for (const module of modules.modules) { + createNgModule(module.module.type, this.injector); + } + this.loadedModulesWithComponents[url].next(modulesWithComponents); + this.loadedModulesWithComponents[url].complete(); + } catch (e) { + console.log(`Unable to parse module from url: ${url}`, e); + this.loadedModulesWithComponents[url].error(new Error(`Unable to parse module from url: ${url}`)); + } + } else { + this.loadedModulesWithComponents[url].error(new Error(`Module '${url}' doesn't have exported modules or components!`)); + } + }, + error: err => { + console.log(`Unable to patch module with flexLayout, module url: ${url}`, err); + this.loadedModulesWithComponents[url].error(new Error(`Unable to patch module with flexLayout, module url: ${url}`)); + } } - this.loadedModulesWithComponents[url].next(modulesWithComponents); - this.loadedModulesWithComponents[url].complete(); - } else { - this.loadedModulesWithComponents[url].error(new Error(`Module '${url}' doesn't have exported modules or components!`)); - } + ); } catch (e) { console.log(`Unable to parse module from url: ${url}`, e); this.loadedModulesWithComponents[url].error(new Error(`Unable to parse module from url: ${url}`)); @@ -284,6 +305,40 @@ export class ResourcesService { return modulesWithComponents; } + private patchModulesWithFlexLayout(modulesWithComponents: ModulesWithComponents): Observable { + return getFlexLayoutModule().pipe( + map((flexLayoutModule) => { + modulesWithComponents.modules.forEach(m => { + if (Array.isArray(m.module.imports)) { + if (!m.module.imports.includes(flexLayoutModule)) { + m.module.imports.push(flexLayoutModule); + } + } else { + const imports = m.module.imports(); + if (!imports.includes(flexLayoutModule)) { + imports.push(flexLayoutModule); + m.module.imports = imports; + } + } + }); + modulesWithComponents.standaloneComponents.forEach(c => { + if (Array.isArray(c.dependencies)) { + if (!c.dependencies.includes(flexLayoutModule)) { + c.dependencies.push(flexLayoutModule); + } + } else { + const dependencies = c.dependencies(); + if (!dependencies.includes(flexLayoutModule)) { + dependencies.push(flexLayoutModule); + c.dependencies = dependencies; + } + } + }); + return modulesWithComponents; + }) + ); + } + private loadResourceByType(type: 'css' | 'js', url: string): Observable { const subject = new ReplaySubject(); this.loadedResources[url] = subject; diff --git a/ui-ngx/src/app/core/settings/settings.utils.ts b/ui-ngx/src/app/core/settings/settings.utils.ts index 9db2994fe1..8f0a261d08 100644 --- a/ui-ngx/src/app/core/settings/settings.utils.ts +++ b/ui-ngx/src/app/core/settings/settings.utils.ts @@ -21,6 +21,9 @@ import { Observable } from 'rxjs'; export function updateUserLang(translate: TranslateService, userLang: string, translations = env.supportedLangs): Observable { let targetLang = userLang; + if (!translations) { + translations = env.supportedLangs; + } if (!env.production) { console.log(`User lang: ${targetLang}`); } diff --git a/ui-ngx/src/app/modules/common/modules-map.models.ts b/ui-ngx/src/app/modules/common/modules-map.models.ts index e1c88e557a..8913996851 100644 --- a/ui-ngx/src/app/modules/common/modules-map.models.ts +++ b/ui-ngx/src/app/modules/common/modules-map.models.ts @@ -14,6 +14,8 @@ /// limitations under the License. /// +import { Observable } from 'rxjs'; + export interface IModulesMap { - init(): void; + init(): Observable; } diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 76bc84bbec..12041a71da 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -20,10 +20,6 @@ import * as AngularAnimations from '@angular/animations'; import * as AngularCore from '@angular/core'; import * as AngularCommon from '@angular/common'; import * as AngularForms from '@angular/forms'; -import * as AngularFlexLayout from '@angular/flex-layout'; -import * as AngularFlexLayoutFlex from '@angular/flex-layout/flex'; -import * as AngularFlexLayoutGrid from '@angular/flex-layout/grid'; -import * as AngularFlexLayoutExtended from '@angular/flex-layout/extended'; import * as AngularPlatformBrowser from '@angular/platform-browser'; import * as AngularPlatformBrowserAnimations from '@angular/platform-browser/animations'; import * as AngularRouter from '@angular/router'; @@ -196,6 +192,8 @@ import * as HintTooltipIconComponent from '@shared/components/hint-tooltip-icon. import * as ScrollGridComponent from '@shared/components/grid/scroll-grid.component'; import * as GalleryImageInputComponent from '@shared/components/image/gallery-image-input.component'; import * as MultipleGalleryImageInputComponent from '@shared/components/image/multiple-gallery-image-input.component'; +import * as TbPopoverService from '@shared/components/popover.service'; + import * as CssUnitSelectComponent from '@home/components/widget/lib/settings/common/css-unit-select.component'; import * as WidgetActionsPanelComponent from '@home/components/widget/config/basic/common/widget-actions-panel.component'; @@ -247,6 +245,7 @@ import * as CustomActionPrettyEditorComponent from '@home/components/widget/lib/ import * as MobileActionEditorComponent from '@home/components/widget/lib/settings/common/action/mobile-action-editor.component'; import * as CustomDialogService from '@home/components/widget/dialog/custom-dialog.service'; import * as CustomDialogContainerComponent from '@home/components/widget/dialog/custom-dialog-container.component'; +import * as ImportExportService from '@shared/import-export/import-export.service'; import * as ImportDialogComponent from '@shared/import-export/import-dialog.component'; import * as AddWidgetToDashboardDialogComponent from '@home/components/attribute/add-widget-to-dashboard-dialog.component'; import * as ImportDialogCsvComponent from '@shared/import-export/import-dialog-csv.component'; @@ -338,6 +337,8 @@ import { IModulesMap } from '@modules/common/modules-map.models'; import { TimezoneComponent } from '@shared/components/time/timezone.component'; import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; +import { Observable, map, of } from 'rxjs'; +import { getFlexLayout } from '@shared/legacy/flex-layout.models'; class ModulesMap implements IModulesMap { @@ -349,10 +350,10 @@ class ModulesMap implements IModulesMap { '@angular/common': AngularCommon, '@angular/common/http': HttpClientModule, '@angular/forms': AngularForms, - '@angular/flex-layout': AngularFlexLayout, - '@angular/flex-layout/flex': AngularFlexLayoutFlex, - '@angular/flex-layout/grid': AngularFlexLayoutGrid, - '@angular/flex-layout/extended': AngularFlexLayoutExtended, + '@angular/flex-layout': {}, + '@angular/flex-layout/flex': {}, + '@angular/flex-layout/grid': {}, + '@angular/flex-layout/extended': {}, '@angular/platform-browser': AngularPlatformBrowser, '@angular/platform-browser/animations': AngularPlatformBrowserAnimations, '@angular/router': AngularRouter, @@ -433,6 +434,7 @@ class ModulesMap implements IModulesMap { '@shared/decorators/enumerable': enumerable, '@shared/decorators/tb-inject': TbInject, + '@shared/import-export/import-export.service': ImportExportService, '@shared/import-export/import-dialog.component': ImportDialogComponent, '@shared/import-export/import-dialog-csv.component': ImportDialogCsvComponent, '@shared/import-export/table-columns-assignment.component': TableColumnsAssignmentComponent, @@ -531,6 +533,8 @@ class ModulesMap implements IModulesMap { '@shared/components/grid/scroll-grid.component': ScrollGridComponent, '@shared/components/image/gallery-image-input.component': GalleryImageInputComponent, '@shared/components/image/multiple-gallery-image-input.component': MultipleGalleryImageInputComponent, + '@shared/components/popover.service': TbPopoverService, + '@home/components/alarm/alarm-filter-config.component': AlarmFilterConfigComponent, '@home/components/alarm/alarm-comment-dialog.component': AlarmCommentDialogComponent, @@ -669,30 +673,40 @@ class ModulesMap implements IModulesMap { '@home/components/queue/queue-form.component': QueueFormComponent }; - init() { + init(): Observable { if (!this.initialized) { - System.constructor.prototype.resolve = (id: string) => { - try { - if (this.modulesMap[id]) { - return 'app:' + id; - } else { - return id; + return getFlexLayout().pipe( + map((flexLayout) => { + this.modulesMap['@angular/flex-layout'] = flexLayout; + this.modulesMap['@angular/flex-layout/flex'] = flexLayout; + this.modulesMap['@angular/flex-layout/grid'] = flexLayout; + this.modulesMap['@angular/flex-layout/extended'] = flexLayout; + System.constructor.prototype.resolve = (id: string) => { + try { + if (this.modulesMap[id]) { + return 'app:' + id; + } else { + return id; + } + } catch (err) { + return id; + } + }; + for (const moduleId of Object.keys(this.modulesMap)) { + System.set('app:' + moduleId, this.modulesMap[moduleId]); } - } catch (err) { - return id; - } - }; - for (const moduleId of Object.keys(this.modulesMap)) { - System.set('app:' + moduleId, this.modulesMap[moduleId]); - } - System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); - System.constructor.prototype.fetch = (url: string, options: RequestInit & {meta?: any}) => { - if (options?.meta?.additionalHeaders) { - options.headers = { ...options.headers, ...options.meta.additionalHeaders }; - } - return fetch(url, options); - }; - this.initialized = true; + System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); + System.constructor.prototype.fetch = (url: string, options: RequestInit & {meta?: any}) => { + if (options?.meta?.additionalHeaders) { + options.headers = { ...options.headers, ...options.meta.additionalHeaders }; + } + return fetch(url, options); + }; + this.initialized = true; + }) + ); + } else { + return of(null); } } } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html index ffd0a196e9..d16176e7f4 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html @@ -35,11 +35,11 @@ {{ assignedToCurrentUserText | translate }} + [class.!hidden]="assigneeId === user.id.id"> -
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html index 903b473dbe..8ef4044dac 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html @@ -16,16 +16,16 @@ --> - alarm.assignee - + {{ getUserInitials() }} - account_circle - arrow_drop_down + account_circle + arrow_drop_down diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html index 229566a3ae..7fb20305bd 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - alarm.assignee diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html index 8145c31b06..293f7efe08 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -17,7 +17,7 @@ -->
- + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html index d98e960081..1fb162539e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html @@ -15,10 +15,11 @@ limitations under the License. --> -
-
-
+
+
+
alarm-activity.activity @@ -47,13 +48,14 @@
-
+
-
-
+
+
{{ displayDataElement.commentText }} @@ -65,20 +67,20 @@
-
-
{{ getUserInitials(displayDataElement.displayName) }}
- no_accounts + no_accounts -
-
+
+
{{ displayDataElement.displayName }} {{ displayDataElement.commentText }}
-
+
-
-
+
{{ getUserInitials(displayDataElement.displayName) }}
- + -
+
-
-
+
+
{{ getUserInitials(userDisplayName) }}
- + @@ -38,7 +38,7 @@
-
+ -
+
-
+
-
+
@@ -83,7 +83,7 @@ [ruleChainType]="edgeRuleChainType"> {{'asset-profile.default-edge-rule-chain-hint' | translate}} - diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html index cae46d2864..fc24273b78 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html @@ -25,7 +25,7 @@ (keydown)="deviceProfileEnter($event)" (keypress)="deviceProfileEnter($event)" [matAutocomplete]="deviceProfileAutocomplete" - [fxShow]="!showDetailsPageLink || !useDeviceProfileLink || !disabled || !selectDeviceProfileFormGroup.get('deviceProfile').value"> + [class.!hidden]="showDetailsPageLink && useDeviceProfileLink && disabled && selectDeviceProfileFormGroup.get('deviceProfile').value"> {{ displayDeviceProfileFn(selectDeviceProfileFormGroup.get('deviceProfile').value) }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html index ed1704e47d..d9066a7fd6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html @@ -18,7 +18,7 @@

{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}

- +
-
+
-
+
-
+
@@ -97,7 +97,7 @@ [deviceProfileId]="deviceProfileId?.id" formControlName="softwareId"> - + device-profile.type @@ -108,7 +108,7 @@ {{ 'device-profile.type-required' | translate }} - diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html index 89cc3d8be9..b3ef45c672 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html @@ -19,7 +19,7 @@
device-profile.coap-device-type -
+
@@ -35,7 +35,7 @@
device-profile.coap-device-payload-type -
+
@@ -46,10 +46,10 @@ {{ 'device-profile.mqtt-payload-type-required' | translate }} -
+
@@ -60,7 +60,7 @@ @@ -71,7 +71,7 @@ @@ -82,7 +82,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html b/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html index 2b8dc0023c..237aad2d1a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html @@ -16,7 +16,7 @@ -->
- + {{ "device-profile.power-saving-mode" | translate }} {{ "device-profile.power-saving-mode-type.default" | translate }} @@ -25,7 +25,7 @@ -
+
-
- +
+ {{ labelText | translate }} @@ -29,7 +29,7 @@ {{ (minText || patternText) | translate : {min: minTime/1000} }} - + device-profile.condition-duration-time-unit diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html index 1dd9d4ccfd..cf2848648a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html @@ -20,7 +20,7 @@

{{ (readonly ? 'device-profile.lwm2m.view-attributes' : 'device-profile.lwm2m.edit-attributes') | translate : {name: name} }}

- +
-
+
-
device-profile.lwm2m.no-attributes-set
+
device-profile.lwm2m.no-attributes-set
-
-
+
+
{{ 'device-profile.lwm2m.server-type' | translate }} @@ -37,8 +37,8 @@
-
- +
+
-
+
- device-profile.snmp.please-add-communication-config + device-profile.snmp.please-add-communication-config
- tenant-profile.no-queue + tenant-profile.no-queue
-
+
-
+
-
+
@@ -58,7 +58,7 @@ {{ 'tenant-profile.name-max-length' | translate }} -
+
{{ 'tenant.isolated-tb-rule-engine' | translate }}
{{'tenant.isolated-tb-rule-engine-details' | translate}}
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 31724191cf..d511125eec 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -15,13 +15,13 @@ limitations under the License. --> -
+
{{ 'tenant-profile.entities' | translate }} tenant-profile.unlimited -
- +
+ tenant-profile.maximum-devices - + tenant-profile.maximum-dashboards
-
- +
+ tenant-profile.maximum-assets - + tenant-profile.maximum-users - + tenant-profile.advanced-settings -
- +
+ tenant-profile.maximum-customers - + tenant-profile.maximum-rule-chains {{ 'tenant-profile.rule-engine' | translate }} tenant-profile.unlimited -
- +
+ tenant-profile.max-r-e-executions - + tenant-profile.max-transport-messages - + tenant-profile.advanced-settings -
- +
+ tenant-profile.max-j-s-executions - + tenant-profile.max-tbel-executions
-
- +
+ tenant-profile.max-rule-node-executions-per-message - + tenant-profile.max-transport-data-points {{ 'tenant-profile.time-to-live' | translate }} tenant-profile.unlimited -
- +
+ tenant-profile.max-d-p-storage-days - + tenant-profile.alarms-ttl-days
-
- +
+ tenant-profile.default-storage-ttl-days - + tenant-profile.rpc-ttl-days
-
- +
+ tenant-profile.queue-stats-ttl-days - + tenant-profile.rule-engine-exceptions-ttl-days {{ 'tenant-profile.alarms-and-notifications' | translate }} tenant-profile.unlimited - + {{ 'tenant-profile.sms-enabled' | translate }} - tenant-profile.max-sms -
- +
+ tenant-profile.max-emails - + tenant-profile.max-created-alarms {{ 'tenant-profile.ota-files-in-bytes' | translate }} tenant-profile.unlimited -
- +
+ tenant-profile.maximum-resources-sum-data-size - + tenant-profile.maximum-ota-packages-sum-data-size
-
- +
+ tenant-profile.maximum-resource-size -
+
@@ -408,15 +408,15 @@ {{ 'tenant-profile.ws-title' | translate }} tenant-profile.unlimited -
- +
+ tenant-profile.ws-limit-max-sessions-per-tenant {{ 'tenant-profile.too-small-value-zero' | translate}} - + tenant-profile.ws-limit-max-subscriptions-per-tenant @@ -424,15 +424,15 @@
-
- +
+ tenant-profile.ws-limit-max-sessions-per-customer {{ 'tenant-profile.too-small-value-zero' | translate}} - + tenant-profile.ws-limit-max-subscriptions-per-customer @@ -442,20 +442,20 @@
- + tenant-profile.advanced-settings -
- +
+ tenant-profile.ws-limit-max-sessions-per-public-user {{ 'tenant-profile.too-small-value-zero' | translate}} - + tenant-profile.ws-limit-max-subscriptions-per-public-user @@ -463,15 +463,15 @@
-
- +
+ tenant-profile.ws-limit-max-sessions-per-regular-user {{ 'tenant-profile.too-small-value-zero' | translate}} - + tenant-profile.ws-limit-max-subscriptions-per-regular-user @@ -479,8 +479,8 @@
-
- +
+ tenant-profile.ws-limit-queue-per-session @@ -490,7 +490,7 @@ {{ 'tenant-profile.queue-size-is-limited-by-system-configuration' | translate}} -
+
@@ -500,106 +500,106 @@ {{ 'tenant-profile.rate-limits.rate-limits' | translate }} -
- + -
-
- + -
-
- + -
-
- + -
- + tenant-profile.advanced-settings -
- + -
-
- + -
-
- + -
-
- + -
-
- + -
-
- + -
-
- + -
-
- + -
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html index 8c8c261a14..d7636e2c41 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html @@ -18,7 +18,7 @@

{{ title | translate }}

- + -
+
tenant-profile.rate-limits.preview
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html index 39fc900b5e..b0884fc840 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html @@ -15,4 +15,4 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html index 9f4d514f60..4f6b791c7c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html @@ -15,10 +15,10 @@ limitations under the License. --> -
+
{{ label | translate }} -
diff --git a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html index 38e2860cd5..0165f75ccc 100644 --- a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html +++ b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + admin.queue-name @@ -37,10 +37,10 @@ -
-
+
+
- + {{ queueSubmitStrategyTypesMap.get(queueSubmitStrategyTypes[strategy]).label | translate }}
-
+
queue.batch-size @@ -75,10 +75,10 @@ -
-
+
+
- + {{ queueProcessingStrategyTypesMap.get(queueProcessingStrategyTypes[strategy]).label | translate }}
-
+
queue.retries @@ -156,11 +156,11 @@ -
-
+
+
-
- +
+ queue.poll-interval @@ -171,7 +171,7 @@ {{ 'queue.poll-interval-min-value' | translate }} - + queue.partitions @@ -184,13 +184,13 @@
-
+
-
- +
+ {{ 'queue.consumer-per-partition' | translate }} - + queue.processing-timeout @@ -208,7 +208,7 @@
- + {{ 'queue.duplicate-msg-to-all-partitions' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index 435f5a42ee..b6bb288b9f 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -16,9 +16,9 @@ --> - +

{{ (isAdd ? 'relation.add' : 'relation.edit' ) | translate }}

- +
-
- +
+
-
- relation.any-relation +
+ relation.any-relation
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html index 5be9f4932c..fb49f299e9 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -16,10 +16,10 @@ -->
-
- +
+
-
+
{{(direction == directions.FROM ? 'relation.from-relations' : 'relation.to-relations') | translate}} @@ -33,7 +33,7 @@
- +
- +
- +  
- -
+ +
{{ 'relation.selected-relations' | translate:{count: dataSource.selection.selected.length} }} @@ -85,7 +85,7 @@
-
+
@@ -140,7 +140,7 @@ -
+
- {{ 'relation.no-relations-text' }} + {{ 'relation.no-relations-text' }}
-
-
-
-
+
- +
-
+
- +
- +  
-
+
-
+
- {{ 'widget-config.no-actions-text' }} + {{ 'widget-config.no-actions-text' }}

{{ (isAdd ? 'widget-config.add-action' : 'widget-config.edit-action' ) | translate }}

- +
-
+
- {{ noKeysText }} + {{ noKeysText }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html index d2a2e59cb2..57af5716aa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html @@ -16,9 +16,9 @@ -->
-
+
widget-config.actions
- + {{ widgetAction.icon }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/entity/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/entity/entities-table-basic-config.component.html index 1c3cd26863..903df04aba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/entity/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/entity/entities-table-basic-config.component.html @@ -43,8 +43,8 @@ {{ 'widget-config.card-title' | translate }} -
- +
+ {{ 'widget-config.card-icon' | translate }} -
+
{{ 'widgets.gauge.units-title' | translate }} -
+
@@ -51,7 +51,7 @@
widget-config.units-short
-
+
@@ -70,10 +70,10 @@ {{ 'widgets.gauge.value' | translate }} -
- +
+ -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
widgets.gauge.ticks
-
+
widgets.gauge.min-value-short
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/compass-gauge-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/compass-gauge-basic-config.component.html index 02e47db0f8..46f7553d42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/compass-gauge-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/compass-gauge-basic-config.component.html @@ -35,8 +35,8 @@ {{ 'widget-config.title' | translate }} -
- +
+ {{ 'widget-config.icon' | translate }} -
+
widgets.gauge.compass-appearance
widgets.gauge.labels
-
+
widgets.gauge.label-style
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html index 11e0d6abc2..1f5d3996f0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html @@ -55,7 +55,7 @@ {{ 'widgets.gauge.min-and-max-label' | translate }} -
+
@@ -68,7 +68,7 @@
{{ 'widgets.gauge.min-and-max-value' | translate }}
-
+
widgets.gauge.min-value-short
@@ -92,14 +92,14 @@ {{ 'widgets.gauge.value' | translate }} -
+
- + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
{{ 'widgets.gauge.label' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html index f5a5693a63..7ac93f9d0c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html @@ -52,7 +52,7 @@ {{ 'widget-config.title' | translate }} -
+
@@ -71,11 +71,11 @@ {{ 'widgets.battery-level.icon' | translate }} -
- +
+ - + {{ 'widgets.battery-level.value' | translate }} -
+
{{ 'widgets.battery-level.auto-scale' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index a3bfce9d5d..9808548bbe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -50,10 +50,10 @@ {{ 'widgets.liquid-level-card.icon' | translate }}
- + - +
-
+
widgets.liquid-level-card.shape
@@ -73,7 +73,7 @@
+
widgets.liquid-level-card.total-volume
@@ -165,7 +165,7 @@ + appearance="outline" class="number flex" subscriptSizing="dynamic">
-
+
widgets.liquid-level-card.total-volume-units
@@ -227,10 +227,10 @@ *ngIf="levelCardWidgetConfigForm.get('layout').value !== LevelCardLayout.simple">
widgets.liquid-level-card.value
- + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -254,10 +254,9 @@ - + - + {{ 'widget-config.tooltip' | translate }} @@ -272,10 +271,10 @@ [tagFilter]="unitsType.capacity" formControlName="tooltipUnits"> - + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -289,7 +288,7 @@ {{ 'widgets.value-card.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html index 0e8475aacf..6a147c18db 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html @@ -46,7 +46,7 @@ {{ 'widget-config.title' | translate }} -
+
@@ -65,12 +65,12 @@ {{ 'widgets.signal-strength.icon' | translate }} -
- +
+ - + {{ 'widgets.signal-strength.date' | translate }} -
- +
+ @@ -113,10 +113,9 @@
- + - + {{ 'widget-config.tooltip' | translate }} @@ -126,12 +125,12 @@ {{ 'tooltip.value' | translate }} -
+
- + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -146,8 +145,8 @@ {{ 'tooltip.date' | translate }} -
- +
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html index e43dd1665a..4233427e8c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html @@ -21,7 +21,7 @@
widgets.status-widget.behavior
widgets.rpc-state.initial-state
-
widgets.rpc-state.disabled-state
-
-
+
widget-config.card-style
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 9fa2d1b1a5..187a1f4a16 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -21,7 +21,7 @@
widgets.single-switch.behavior
widgets.rpc-state.initial-state
-
widgets.rpc-state.turn-on
-
widgets.rpc-state.turn-off
-
widgets.rpc-state.disabled-state
- {{ 'widgets.single-switch.label' | translate }} -
+
@@ -103,11 +103,11 @@ {{ 'widgets.single-switch.icon' | translate }} -
- +
+ - +
{{ 'widgets.single-switch.switch-color' | translate }}
-
-
+
+
widgets.single-switch.on
- -
+ +
widgets.single-switch.off
- -
+ +
widgets.single-switch.disabled
@@ -146,22 +146,22 @@
{{ 'widgets.single-switch.tumbler-color' | translate }}
-
-
+
+
widgets.single-switch.on
- -
+ +
widgets.single-switch.off
- -
+ +
widgets.single-switch.disabled
@@ -173,7 +173,7 @@ {{ 'widgets.single-switch.on-label' | translate }} -
+
@@ -190,7 +190,7 @@ {{ 'widgets.single-switch.off-label' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html index 5dd62493ec..8c49a4809e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html @@ -21,7 +21,7 @@
widgets.slider.behavior
widgets.slider.initial-value
-
widgets.slider.on-value-change
-
widgets.rpc-state.disabled-state
- {{ 'widget-config.title' | translate }} -
+
@@ -94,11 +94,11 @@ {{ 'widgets.slider.icon' | translate }} -
- +
+ - + {{ 'widgets.slider.value' | translate }} -
+
- + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -131,7 +131,7 @@
{{ 'widgets.slider.range' | translate }}
-
+
widgets.slider.min
@@ -146,7 +146,7 @@ {{ 'widgets.slider.range-ticks' | translate }} -
+
@@ -160,7 +160,7 @@ {{ 'widgets.slider.tick-marks' | translate }} -
+
@@ -172,15 +172,15 @@
{{ 'widgets.slider.colors' | translate }}
-
-
+
+
widgets.slider.main
-
+
widgets.slider.background
@@ -190,15 +190,15 @@
{{ 'widgets.rpc-state.disabled-state' | translate }}
-
-
+
+
widgets.slider.main
-
+
widgets.slider.background
@@ -211,11 +211,11 @@
{{ 'widgets.slider.left-icon' | translate }}
-
- +
+ - + @@ -230,11 +230,11 @@
{{ 'widgets.slider.right-icon' | translate }}
-
- +
+ - + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html index e9c6f0967e..d3e31fe189 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html @@ -34,7 +34,7 @@ {{ 'widget-config.title' | translate }} -
+
@@ -53,11 +53,11 @@ {{ 'widget-config.card-icon' | translate }} -
- +
+ - + widgets.wind-speed-direction.values
widgets.wind-speed-direction.wind-direction
-
- +
widgets.wind-speed-direction.center-value
-
- + {{ 'widget-config.title' | translate }} -
+
@@ -113,11 +113,11 @@ {{ 'widgets.wind-speed-direction.icon' | translate }} -
- +
+ - +
{{ 'widgets.wind-speed-direction.major-ticks' | translate }}
-
- +
{{ 'widgets.wind-speed-direction.minor-ticks' | translate }}
-
- +

{{ 'datakey.configuration' | translate }}

- + @@ -53,7 +53,7 @@ formControlName="dataKey">
-
+
@@ -26,11 +26,11 @@
-
+
-
+
- +  
- +
{{ translate.get('alarm.selected-alarms', {count: alarmsDatasource.selection.selected.length}) | async }} - +
-
+
@@ -78,16 +78,16 @@ - + {{ column.title }} - + @@ -120,60 +120,64 @@ - + + +
+
+
+
+
- -
- - - - -
-
- - + + +
- - -
+
+
+ + + + + + +
+
- - +
- {{ noDataDisplayMessageText }} - {{ 'common.loading' | translate }} + {{ noDataDisplayMessageText }} + {{ 'common.loading' | translate }}
- +
-
widgets.notification.notification-types
- +
widgets.notification.notification-types
+
-
+
- + - + widgets.date-range-navigator.localizationMap.Step size diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html index c0902af28e..8096a4d864 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.html index abbf62f973..c94e1cf83a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.html @@ -16,10 +16,10 @@ -->
-
- {{ customerTitle }} - edge.widget-datasource-error -
+
+ {{ customerTitle }} + edge.widget-datasource-error +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entity/entities-hierarchy-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entity/entities-hierarchy-widget.component.html index 7f30329e07..599010d76c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entity/entities-hierarchy-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entity/entities-hierarchy-widget.component.html @@ -16,15 +16,15 @@ -->
-
- +
+
- +  
-
+
-
- +
+
- +  
-
+
- {{ column.title }} + {{ column.title }} - + + +
+
+
+
+
- -
- - - - -
-
- - + + +
- - -
+
+
+ + + + + + +
+
-
- {{ noDataDisplayMessageText }} - {{ 'common.loading' | translate }} + {{ noDataDisplayMessageText }} + {{ 'common.loading' | translate }}
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts index 389deee749..ec40f4b069 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts @@ -46,7 +46,7 @@ export class FlotWidgetComponent implements OnInit { legendConfig: LegendConfig; legendData: LegendData; isLegendFirst: boolean; - legendContainerLayoutType: string; + legendContainerLayoutType: 'flex-row' | 'flex-col'; legendStyle: {[klass: string]: any}; public settings: TbFlotSettings; @@ -70,7 +70,7 @@ export class FlotWidgetComponent implements OnInit { this.displayLegend = isDefinedAndNotNull(this.settings.showLegend) ? this.settings.showLegend : false; - this.legendContainerLayoutType = 'column'; + this.legendContainerLayoutType = 'flex-col'; if (this.displayLegend) { this.legendConfig = this.settings.legendConfig || defaultLegendConfig(widgetType.timeseries); @@ -84,10 +84,10 @@ export class FlotWidgetComponent implements OnInit { } if (this.legendConfig.position === LegendPosition.top || this.legendConfig.position === LegendPosition.bottom) { - this.legendContainerLayoutType = 'column'; + this.legendContainerLayoutType = 'flex-col'; this.isLegendFirst = this.legendConfig.position === LegendPosition.top; } else { - this.legendContainerLayoutType = 'row'; + this.legendContainerLayoutType = 'flex-row'; this.isLegendFirst = this.legendConfig.position === LegendPosition.left; } switch (this.legendConfig.position) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts deleted file mode 100644 index 4c90eeb01c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts +++ /dev/null @@ -1,77 +0,0 @@ -/// -/// 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 { AfterViewInit, Directive, EventEmitter, inject, Input, OnDestroy, Output, TemplateRef } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -@Directive() -export abstract class GatewayConnectorBasicConfigDirective - implements AfterViewInit, ControlValueAccessor, Validator, OnDestroy { - - @Input() generalTabContent: TemplateRef; - @Output() initialized = new EventEmitter(); - - basicFormGroup: FormGroup; - - protected fb = inject(FormBuilder); - protected onChange!: (value: OutputBasicConfig) => void; - protected onTouched!: () => void; - protected destroy$ = new Subject(); - - constructor() { - this.basicFormGroup = this.initBasicFormGroup(); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe((value) => this.onBasicFormGroupChange(value)); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - ngAfterViewInit(): void { - this.initialized.emit(); - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { basicFormGroup: { valid: false } }; - } - - registerOnChange(fn: (value: OutputBasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(config: OutputBasicConfig): void { - this.basicFormGroup.setValue(this.mapConfigToFormValue(config), { emitEvent: false }); - } - - protected onBasicFormGroupChange(value: InputBasicConfig): void { - this.onChange(this.getMappedValue(value)); - this.onTouched(); - } - - protected abstract mapConfigToFormValue(config: OutputBasicConfig): InputBasicConfig; - protected abstract getMappedValue(config: InputBasicConfig): OutputBasicConfig; - protected abstract initBasicFormGroup(): FormGroup; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts deleted file mode 100644 index 93baf124a3..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts +++ /dev/null @@ -1,69 +0,0 @@ -/// -/// 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 { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { - GatewayConnectorVersionMappingUtil -} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util'; - -export abstract class GatewayConnectorVersionProcessor { - gatewayVersion: number; - configVersion: number; - - protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector) { - this.gatewayVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.gatewayVersionIn); - this.configVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.connector.configVersion); - } - - getProcessedByVersion(): GatewayConnector { - if (!this.isVersionUpdateNeeded()) { - return this.connector; - } - - return this.processVersionUpdate(); - } - - private processVersionUpdate(): GatewayConnector { - if (this.isVersionUpgradeNeeded()) { - return this.getUpgradedVersion(); - } else if (this.isVersionDowngradeNeeded()) { - return this.getDowngradedVersion(); - } - - return this.connector; - } - - private isVersionUpdateNeeded(): boolean { - if (!this.gatewayVersion) { - return false; - } - - return this.configVersion !== this.gatewayVersion; - } - - private isVersionUpgradeNeeded(): boolean { - return this.gatewayVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current) - && (!this.configVersion || this.configVersion < this.gatewayVersion); - } - - private isVersionDowngradeNeeded(): boolean { - return this.configVersion && this.configVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current) - && (this.configVersion > this.gatewayVersion); - } - - protected abstract getDowngradedVersion(): GatewayConnector; - protected abstract getUpgradedVersion(): GatewayConnector; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts deleted file mode 100644 index 8b05572659..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ /dev/null @@ -1,71 +0,0 @@ -/// -/// 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 { - GatewayConnector, - LegacySlaveConfig, - ModbusBasicConfig, - ModbusBasicConfig_v3_5_2, - ModbusLegacyBasicConfig, - ModbusLegacySlave, - ModbusMasterConfig, - ModbusSlave, -} from '../gateway-widget.models'; -import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; -import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; - -export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor { - - constructor( - protected gatewayVersionIn: string, - protected connector: GatewayConnector - ) { - super(gatewayVersionIn, connector); - } - - getUpgradedVersion(): GatewayConnector { - const configurationJson = this.connector.configurationJson; - return { - ...this.connector, - configurationJson: { - master: configurationJson.master?.slaves - ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master as ModbusMasterConfig) - : { slaves: [] }, - slave: configurationJson.slave - ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) - : {} as ModbusSlave, - }, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } - - getDowngradedVersion(): GatewayConnector { - const configurationJson = this.connector.configurationJson; - return { - ...this.connector, - configurationJson: { - ...configurationJson, - slave: configurationJson.slave - ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave) - : {} as ModbusLegacySlave, - master: configurationJson.master?.slaves - ? ModbusVersionMappingUtil.mapMasterToDowngradedVersion(configurationJson.master as ModbusMasterConfig) - : { slaves: [] }, - }, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts deleted file mode 100644 index 00fff92493..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts +++ /dev/null @@ -1,101 +0,0 @@ -/// -/// 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 { isEqual } from '@core/utils'; -import { - GatewayConnector, - MQTTBasicConfig, - MQTTBasicConfig_v3_5_2, - MQTTLegacyBasicConfig, - RequestMappingData, - RequestType, -} from '../gateway-widget.models'; -import { MqttVersionMappingUtil } from '../utils/mqtt-version-mapping.util'; -import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; - -export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { - - private readonly mqttRequestTypeKeys = Object.values(RequestType); - - constructor( - protected gatewayVersionIn: string, - protected connector: GatewayConnector - ) { - super(gatewayVersionIn, connector); - } - - getUpgradedVersion(): GatewayConnector { - const { - connectRequests, - disconnectRequests, - attributeRequests, - attributeUpdates, - serverSideRpc - } = this.connector.configurationJson as MQTTLegacyBasicConfig; - let configurationJson = { - ...this.connector.configurationJson, - requestsMapping: MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ - connectRequests, - disconnectRequests, - attributeRequests, - attributeUpdates, - serverSideRpc - }), - mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion((this.connector.configurationJson as MQTTLegacyBasicConfig).mapping), - }; - - this.mqttRequestTypeKeys.forEach((key: RequestType) => { - const { [key]: removedValue, ...rest } = configurationJson as MQTTLegacyBasicConfig; - configurationJson = { ...rest } as any; - }); - - this.cleanUpConfigJson(configurationJson as MQTTBasicConfig_v3_5_2); - - return { - ...this.connector, - configurationJson, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } - - getDowngradedVersion(): GatewayConnector { - const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2; - - const updatedRequestsMapping = requestsMapping - ? MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record) : {}; - const updatedMapping = MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping); - - return { - ...this.connector, - configurationJson: { - ...restConfig, - ...updatedRequestsMapping, - mapping: updatedMapping, - }, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } - - private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void { - if (isEqual(configurationJson.requestsMapping, {})) { - delete configurationJson.requestsMapping; - } - - if (isEqual(configurationJson.mapping, [])) { - delete configurationJson.mapping; - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts deleted file mode 100644 index 541e26001f..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts +++ /dev/null @@ -1,56 +0,0 @@ -/// -/// 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 { - GatewayConnector, LegacyServerConfig, - OPCBasicConfig, - OPCBasicConfig_v3_5_2, - OPCLegacyBasicConfig, -} from '../gateway-widget.models'; -import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; -import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; - -export class OpcVersionProcessor extends GatewayConnectorVersionProcessor { - - constructor( - protected gatewayVersionIn: string, - protected connector: GatewayConnector - ) { - super(gatewayVersionIn, connector); - } - - getUpgradedVersion(): GatewayConnector { - const server = this.connector.configurationJson.server as LegacyServerConfig; - return { - ...this.connector, - configurationJson: { - server: server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(server) : {}, - mapping: server?.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(server.mapping) : [], - }, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } - - getDowngradedVersion(): GatewayConnector { - return { - ...this.connector, - configurationJson: { - server: OpcVersionMappingUtil.mapServerToDowngradedVersion(this.connector.configurationJson as OPCBasicConfig_v3_5_2) - }, - configVersion: this.gatewayVersionIn - } as GatewayConnector; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.html deleted file mode 100644 index 843e67b0fe..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.scss deleted file mode 100644 index 4825ff0a64..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 { - .config-container { - height: calc(100% - 60px); - padding: 8px; - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.ts deleted file mode 100644 index 16a0274049..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -/// -/// 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 { Component, forwardRef, OnDestroy } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validators -} from '@angular/forms'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { GatewayConfigValue } from '@home/components/widget/lib/gateway/configuration/models/gateway-configuration.models'; - -@Component({ - selector: 'tb-gateway-advanced-configuration', - templateUrl: './gateway-advanced-configuration.component.html', - styleUrls: ['./gateway-advanced-configuration.component.scss'], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => GatewayAdvancedConfigurationComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => GatewayAdvancedConfigurationComponent), - multi: true - } - ], -}) -export class GatewayAdvancedConfigurationComponent implements OnDestroy, ControlValueAccessor, Validators { - - advancedFormControl: FormControl; - - private onChange: (value: unknown) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.advancedFormControl = this.fb.control(''); - this.advancedFormControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: unknown) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(advancedConfig: GatewayConfigValue): void { - this.advancedFormControl.reset(advancedConfig, {emitEvent: false}); - } - - validate(): ValidationErrors | null { - return this.advancedFormControl.valid ? null : { - advancedFormControl: {valid: false} - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html deleted file mode 100644 index af8dc3119c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html +++ /dev/null @@ -1,814 +0,0 @@ - - - - -
-
-
- - {{ 'gateway.remote-configuration' | translate }} - -
-
- - {{ 'gateway.remote-shell' | translate }} - -
-
- - gateway.thingsboard-host - - info_outlined - - - {{ 'gateway.thingsboard-host-required' | translate }} - - - - gateway.thingsboard-port - - - {{ 'gateway.thingsboard-port-required' | translate }} - - - {{ 'gateway.thingsboard-port-min' | translate }} - - - {{ 'gateway.thingsboard-port-max' | translate }} - - - {{ 'gateway.thingsboard-port-pattern' | translate }} - - info_outlined - - -
-
-
-
security.security
- - - {{ securityType.value | translate }} - - - - security.access-token - - - {{ 'security.access-token-required' | translate }} - - - - info_outlined - - -
-
- - security.clientId - - - {{ 'security.clientId-required' | translate }} - - - - info_outlined - - - - security.username - - - {{ 'security.username-required' | 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.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.backup-count-required' | translate }} - - - {{ 'gateway.logs.backup-count-min' | translate }} - - info_outlined - - -
-
-
-
-
-
- - -
-
-
gateway.storage
-
gateway.hints.storage
- - - {{ storageTypesTranslationMap.get(storageType) | translate }} - - -
{{ 'gateway.hints.' + basicFormGroup.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-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.messages-ttl-in-days-min' | translate }} - - - {{ 'gateway.messages-ttl-in-days-pattern' | translate }} - - info_outlined - - -
-
-
-
-
-
- - -
-
- - {{ 'gateway.grpc' | translate }} - -
- - {{ 'gateway.permit-without-calls' | translate }} - -
-
-
- - gateway.server-port - - info_outlined - - - {{ 'gateway.thingsboard-port-required' | translate }} - - - {{ 'gateway.thingsboard-port-min' | translate }} - - - {{ 'gateway.thingsboard-port-max' | translate }} - - - {{ 'gateway.thingsboard-port-pattern' | translate }} - - - - gateway.grpc-keep-alive-timeout - - 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.grpc-keep-alive-min' | translate }} - - - {{ 'gateway.grpc-keep-alive-pattern' | translate }} - - - - gateway.grpc-min-time-between-pings - - 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.grpc-max-pings-without-data - - info_outlined - - - {{ 'gateway.grpc-max-pings-without-data-required' | translate }} - - - {{ 'gateway.grpc-max-pings-without-data-min' | translate }} - - - {{ 'gateway.grpc-max-pings-without-data-pattern' | translate }} - - - - gateway.grpc-min-ping-interval-without-data - - 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.statistics.statistics' | translate }} - - - gateway.statistics.send-period - - - {{ 'gateway.statistics.send-period-required' | translate }} - - - {{ 'gateway.statistics.send-period-min' | translate }} - - - {{ 'gateway.statistics.send-period-pattern' | translate }} - - -
-
-
gateway.statistics.commands
-
gateway.hints.commands
- -
-
-
- - gateway.statistics.attribute-name - - - {{ 'gateway.statistics.attribute-name-required' | translate }} - - info_outlined - - - - gateway.statistics.timeout - - - {{ 'gateway.statistics.timeout-required' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - {{ 'gateway.statistics.timeout-pattern' | translate }} - - info_outlined - - -
- - gateway.statistics.command - - - {{ 'gateway.statistics.command-required' | translate }} - - - {{ 'gateway.statistics.command-pattern' | translate }} - - info_outlined - - -
- -
- -
-
-
-
-
- - -
-
-
- - {{ 'gateway.checking-device-activity' | translate }} - -
-
- - gateway.inactivity-timeout-seconds - - - {{ 'gateway.inactivity-timeout-seconds-required' | translate }} - - - {{ 'gateway.inactivity-timeout-seconds-min' | translate }} - - - {{ 'gateway.inactivity-timeout-seconds-pattern' | translate }} - - info_outlined - - - - gateway.inactivity-check-period-seconds - - - {{ 'gateway.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 }} - - - {{ 'gateway.min-pack-send-delay-pattern' | translate }} - - info_outlined - - - - gateway.mqtt-qos - - - {{ 'gateway.mqtt-qos-required' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - - {{ 'gateway.mqtt-qos-range' | translate }} - - info_outlined - - -
-
- - gateway.statistics.check-connectors-configuration - - - {{ 'gateway.statistics.check-connectors-configuration-required' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-min' | translate }} - - - {{ 'gateway.statistics.check-connectors-configuration-pattern' | translate }} - - - - gateway.statistics.max-payload-size-bytes - - - {{ 'gateway.statistics.max-payload-size-bytes-required' | translate }} - - - {{ 'gateway.statistics.max-payload-size-bytes-min' | translate }} - - - {{ 'gateway.statistics.max-payload-size-bytes-pattern' | translate }} - - info_outlined - - -
-
- - gateway.statistics.min-pack-size-to-send - - - {{ 'gateway.statistics.min-pack-size-to-send-required' | translate }} - - - {{ 'gateway.statistics.min-pack-size-to-send-min' | translate }} - - - {{ 'gateway.statistics.min-pack-size-to-send-pattern' | translate }} - - info_outlined - - -
-
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.scss deleted file mode 100644 index 9807fb71c4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.scss +++ /dev/null @@ -1,103 +0,0 @@ -/** - * 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: grid; - grid-template-rows: min-content minmax(auto, 1fr) min-content; - - .configuration-block { - display: flex; - flex-direction: column; - gap: 16px; - max-height: 70vh; - } - - .dialog-mode { - .configuration-block { - max-height: 60vh; - } - } - - .mat-toolbar { - grid-row: 1; - background: transparent; - color: rgba(0, 0, 0, .87) !important; - } - - .tab-group-block { - min-width: 0; - height: 100%; - min-height: 0; - grid-row: 2; - } - - .toggle-group { - margin-right: auto; - } - - .first-capital { - text-transform: capitalize; - } - - textarea { - resize: none; - } - - .saving-period { - flex: 1; - } - - .statistics-container { - width: 100%; - - .command-container { - width: 100%; - } - } - - mat-form-field { - mat-error { - display: none !important; - } - - mat-error:first-child { - display: block !important; - } - } -} - -:host ::ng-deep { - .pointer-event { - pointer-events: all; - } - - .toggle-group span { - padding: 0 25px; - } - - .mat-mdc-form-field-icon-suffix { - color: #E0E0E0; - &:hover { - color: #9E9E9E; - } - } - - .mat-mdc-form-field-icon-suffix { - display: flex; - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts deleted file mode 100644 index 1cff165f8a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts +++ /dev/null @@ -1,568 +0,0 @@ -/// -/// 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 { - ChangeDetectorRef, - Component, - EventEmitter, - forwardRef, - Input, - OnDestroy, - Output -} from '@angular/core'; -import { - ControlValueAccessor, - FormArray, - FormBuilder, - FormControl, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - ValidatorFn, - Validators -} from '@angular/forms'; -import { EntityId } from '@shared/models/id/entity-id'; -import { MatDialog } from '@angular/material/dialog'; -import { - GatewayRemoteConfigurationDialogComponent, - GatewayRemoteConfigurationDialogData -} from '@home/components/widget/lib/gateway/gateway-remote-configuration-dialog'; -import { DeviceService } from '@core/http/device.service'; -import { Subject } from 'rxjs'; -import { take, takeUntil } from 'rxjs/operators'; -import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; -import { - GatewayLogLevel, - GecurityTypesTranslationsMap, - LocalLogsConfigTranslateMap, - LocalLogsConfigs, - LogSavingPeriod, - LogSavingPeriodTranslations, - SecurityTypes, - StorageTypes, - StorageTypesTranslationMap, -} from '../../gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { - GatewayConfigCommand, - GatewayConfigSecurity, - GatewayConfigValue, - LogConfig -} from '@home/components/widget/lib/gateway/configuration/models/gateway-configuration.models'; - -@Component({ - selector: 'tb-gateway-basic-configuration', - templateUrl: './gateway-basic-configuration.component.html', - styleUrls: ['./gateway-basic-configuration.component.scss'], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => GatewayBasicConfigurationComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => GatewayBasicConfigurationComponent), - multi: true - } - ], -}) -export class GatewayBasicConfigurationComponent implements OnDestroy, ControlValueAccessor, Validators { - - @Input() - device: EntityId; - - @coerceBoolean() - @Input() - dialogMode = false; - - @Output() - initialCredentialsUpdated = new EventEmitter(); - - StorageTypes = StorageTypes; - storageTypes = Object.values(StorageTypes); - storageTypesTranslationMap = StorageTypesTranslationMap; - logSavingPeriods = LogSavingPeriodTranslations; - localLogsConfigs = Object.keys(LocalLogsConfigs) as LocalLogsConfigs[]; - localLogsConfigTranslateMap = LocalLogsConfigTranslateMap; - securityTypes = GecurityTypesTranslationsMap; - gatewayLogLevel = Object.values(GatewayLogLevel); - - logSelector: FormControl; - basicFormGroup: FormGroup; - - private onChange: (value: GatewayConfigValue) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, - private deviceService: DeviceService, - private cd: ChangeDetectorRef, - private dialog: MatDialog) { - this.initBasicFormGroup(); - this.observeFormChanges(); - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: GatewayConfigValue) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: GatewayConfigValue): void { - this.basicFormGroup.patchValue(basicConfig, {emitEvent: false}); - this.checkAndFetchCredentials(basicConfig?.thingsboard?.security ?? {} as GatewayConfigSecurity); - if (basicConfig?.grpc) { - this.toggleRpcFields(basicConfig.grpc.enabled); - } - const commands = basicConfig?.thingsboard?.statistics?.commands ?? []; - commands.forEach((command: GatewayConfigCommand) => this.addCommand(command, false)); - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} - }; - } - - private atLeastOneRequired(validator: ValidatorFn, controls: string[] = null) { - return (group: FormGroup): ValidationErrors | null => { - if (!controls) { - controls = Object.keys(group.controls); - } - const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); - - return hasAtLeastOne ? null : {atLeastOne: true}; - }; - } - - private toggleRpcFields(enable: boolean): void { - const grpcGroup = this.basicFormGroup.get('grpc') as FormGroup; - if (enable) { - grpcGroup.get('serverPort').enable({emitEvent: false}); - grpcGroup.get('keepAliveTimeMs').enable({emitEvent: false}); - grpcGroup.get('keepAliveTimeoutMs').enable({emitEvent: false}); - grpcGroup.get('keepalivePermitWithoutCalls').enable({emitEvent: false}); - grpcGroup.get('maxPingsWithoutData').enable({emitEvent: false}); - grpcGroup.get('minTimeBetweenPingsMs').enable({emitEvent: false}); - grpcGroup.get('minPingIntervalWithoutDataMs').enable({emitEvent: false}); - } else { - grpcGroup.get('serverPort').disable({emitEvent: false}); - grpcGroup.get('keepAliveTimeMs').disable({emitEvent: false}); - grpcGroup.get('keepAliveTimeoutMs').disable({emitEvent: false}); - grpcGroup.get('keepalivePermitWithoutCalls').disable({emitEvent: false}); - grpcGroup.get('maxPingsWithoutData').disable({emitEvent: false}); - grpcGroup.get('minTimeBetweenPingsMs').disable({emitEvent: false}); - grpcGroup.get('minPingIntervalWithoutDataMs').disable({emitEvent: false}); - } - } - - private addLocalLogConfig(name: string, config: LogConfig): void { - const localLogsFormGroup = this.basicFormGroup.get('logs.local') as FormGroup; - const configGroup = this.fb.group({ - logLevel: [config.logLevel || GatewayLogLevel.INFO, [Validators.required]], - filePath: [config.filePath || './logs', [Validators.required]], - backupCount: [config.backupCount || 7, [Validators.required, Validators.min(0)]], - savingTime: [config.savingTime || 3, [Validators.required, Validators.min(0)]], - savingPeriod: [config.savingPeriod || LogSavingPeriod.days, [Validators.required]] - }); - localLogsFormGroup.addControl(name, configGroup); - } - - getLogFormGroup(value: string): FormGroup { - return this.basicFormGroup.get(`logs.local.${value}`) as FormGroup; - } - - commandFormArray(): FormArray { - return this.basicFormGroup.get('thingsboard.statistics.commands') as FormArray; - } - - removeCommandControl(index: number, event: PointerEvent): void { - if (event.pointerType === '') { - return; - } - this.commandFormArray().removeAt(index); - this.basicFormGroup.markAsDirty(); - } - - private removeAllSecurityValidators(): void { - const securityGroup = this.basicFormGroup.get('thingsboard.security') as FormGroup; - securityGroup.clearValidators(); - for (const controlsKey in securityGroup.controls) { - if (controlsKey !== 'type') { - securityGroup.controls[controlsKey].clearValidators(); - securityGroup.controls[controlsKey].setErrors(null); - securityGroup.controls[controlsKey].updateValueAndValidity(); - } - } - } - - private removeAllStorageValidators(): void { - const storageGroup = this.basicFormGroup.get('storage') as FormGroup; - for (const storageKey in storageGroup.controls) { - if (storageKey !== 'type') { - storageGroup.controls[storageKey].clearValidators(); - storageGroup.controls[storageKey].setErrors(null); - storageGroup.controls[storageKey].updateValueAndValidity(); - } - } - } - - - private openConfigurationConfirmDialog(): void { - this.deviceService.getDevice(this.device.id).pipe(takeUntil(this.destroy$)).subscribe(gateway => { - this.dialog.open - (GatewayRemoteConfigurationDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - gatewayName: gateway.name - } - }).afterClosed().pipe(take(1)).subscribe( - (res) => { - if (!res) { - this.basicFormGroup.get('thingsboard.remoteConfiguration').setValue(true, {emitEvent: false}); - } - } - ); - }); - } - - addCommand(command?: GatewayConfigCommand, emitEvent: boolean = true): void { - const { attributeOnGateway = null, command: cmd = null, timeout = null } = command || {}; - - const commandFormGroup = this.fb.group({ - attributeOnGateway: [attributeOnGateway, [Validators.required, Validators.pattern(/^[^.\s]+$/)]], - command: [cmd, [Validators.required, Validators.pattern(/^(?=\S).*\S$/)]], - timeout: [timeout, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.pattern(/^[^.\s]+$/)]] - }); - - this.commandFormArray().push(commandFormGroup, { emitEvent }); - } - - private initBasicFormGroup(): void { - this.basicFormGroup = this.fb.group({ - thingsboard: this.initThingsboardFormGroup(), - storage: this.initStorageFormGroup(), - grpc: this.initGrpcFormGroup(), - connectors: this.fb.array([]), - logs: this.initLogsFormGroup(), - }); - } - - private initThingsboardFormGroup(): FormGroup { - return this.fb.group({ - host: [window.location.hostname, [Validators.required, Validators.pattern(/^[^\s]+$/)]], - port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], - remoteShell: [false], - remoteConfiguration: [true], - checkConnectorsConfigurationInSeconds: [60, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - statistics: this.fb.group({ - enable: [true], - statsSendPeriodInSeconds: [3600, [Validators.required, Validators.min(60), Validators.pattern(/^-?[0-9]+$/)]], - commands: this.fb.array([]) - }), - maxPayloadSizeBytes: [8196, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]], - minPackSendDelayMS: [50, [Validators.required, Validators.min(10), Validators.pattern(/^-?[0-9]+$/)]], - minPackSizeToSend: [500, [Validators.required, Validators.min(100), Validators.pattern(/^-?[0-9]+$/)]], - handleDeviceRenaming: [true], - checkingDeviceActivity: this.initCheckingDeviceActivityFormGroup(), - security: this.initSecurityFormGroup(), - qos: [1, [Validators.required, Validators.min(0), Validators.max(1), Validators.pattern(/^[^.\s]+$/)]] - }); - } - - private initStorageFormGroup(): FormGroup { - return this.fb.group({ - type: [StorageTypes.MEMORY, [Validators.required]], - read_records_count: [100, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - max_records_count: [100000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - data_folder_path: ['./data/', [Validators.required]], - max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - max_records_per_file: [10000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - data_file_path: ['./data/data.db', [Validators.required]], - messages_ttl_check_in_hours: [1, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - messages_ttl_in_days: [7, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]] - }); - } - - private initGrpcFormGroup(): FormGroup { - return this.fb.group({ - enabled: [false], - serverPort: [9595, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], - keepAliveTimeMs: [10000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - keepAliveTimeoutMs: [5000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - keepalivePermitWithoutCalls: [true], - maxPingsWithoutData: [0, [Validators.required, Validators.min(0), Validators.pattern(/^-?[0-9]+$/)]], - minTimeBetweenPingsMs: [10000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - minPingIntervalWithoutDataMs: [5000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]] - }); - } - - private initLogsFormGroup(): FormGroup { - return this.fb.group({ - dateFormat: ['%Y-%m-%d %H:%M:%S', [Validators.required, Validators.pattern(/^[^\s].*[^\s]$/)]], - logFormat: [ - '%(asctime)s - |%(levelname)s| - [%(filename)s] - %(module)s - %(funcName)s - %(lineno)d - %(message)s', - [Validators.required, Validators.pattern(/^[^\s].*[^\s]$/)] - ], - type: ['remote', [Validators.required]], - remote: this.fb.group({ - enabled: [false], - logLevel: [GatewayLogLevel.INFO, [Validators.required]] - }), - local: this.fb.group({}) - }); - } - - private initCheckingDeviceActivityFormGroup(): FormGroup { - return this.fb.group({ - checkDeviceInactivity: [false], - inactivityTimeoutSeconds: [200, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - inactivityCheckPeriodSeconds: [500, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]] - }); - } - - private initSecurityFormGroup(): FormGroup { - return this.fb.group({ - type: [SecurityTypes.ACCESS_TOKEN, [Validators.required]], - accessToken: [null, [Validators.required, Validators.pattern(/^[^.\s]+$/)]], - clientId: [null, [Validators.pattern(/^[^.\s]+$/)]], - username: [null, [Validators.pattern(/^[^.\s]+$/)]], - password: [null, [Validators.pattern(/^[^.\s]+$/)]], - caCert: [null], - cert: [null], - privateKey: [null] - }); - } - - private observeFormChanges(): void { - this.observeSecurityPasswordChanges(); - this.observeRemoteConfigurationChanges(); - this.observeDeviceActivityChanges(); - this.observeSecurityTypeChanges(); - this.observeStorageTypeChanges(); - } - - private observeSecurityPasswordChanges(): void { - const securityUsername = this.basicFormGroup.get('thingsboard.security.username'); - this.basicFormGroup.get('thingsboard.security.password').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(password => { - if (password && password !== '') { - securityUsername.setValidators([Validators.required]); - } else { - securityUsername.clearValidators(); - } - securityUsername.updateValueAndValidity({ emitEvent: false }); - }); - } - - private observeRemoteConfigurationChanges(): void { - this.basicFormGroup.get('thingsboard.remoteConfiguration').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(enabled => { - if (!enabled) { - this.openConfigurationConfirmDialog(); - } - }); - - this.logSelector = this.fb.control(LocalLogsConfigs.service); - for (const key of Object.keys(LocalLogsConfigs)) { - this.addLocalLogConfig(key, {} as LogConfig); - } - } - - private observeDeviceActivityChanges(): void { - const checkingDeviceActivityGroup = this.basicFormGroup.get('thingsboard.checkingDeviceActivity') as FormGroup; - checkingDeviceActivityGroup.get('checkDeviceInactivity').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(enabled => { - checkingDeviceActivityGroup.updateValueAndValidity(); - const validators = [Validators.min(1), Validators.required, Validators.pattern(/^-?[0-9]+$/)]; - - if (enabled) { - checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').setValidators(validators); - checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').setValidators(validators); - } else { - checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').clearValidators(); - checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').clearValidators(); - } - checkingDeviceActivityGroup.get('inactivityTimeoutSeconds').updateValueAndValidity({ emitEvent: false }); - checkingDeviceActivityGroup.get('inactivityCheckPeriodSeconds').updateValueAndValidity({ emitEvent: false }); - }); - - this.basicFormGroup.get('grpc.enabled').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { - this.toggleRpcFields(value); - }); - } - - private observeSecurityTypeChanges(): void { - const securityGroup = this.basicFormGroup.get('thingsboard.security') as FormGroup; - - securityGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(type => { - this.removeAllSecurityValidators(); - - switch (type) { - case SecurityTypes.ACCESS_TOKEN: - this.addAccessTokenValidators(securityGroup); - break; - case SecurityTypes.TLS_PRIVATE_KEY: - this.addTlsPrivateKeyValidators(securityGroup); - break; - case SecurityTypes.TLS_ACCESS_TOKEN: - this.addTlsAccessTokenValidators(securityGroup); - break; - case SecurityTypes.USERNAME_PASSWORD: - securityGroup.addValidators([this.atLeastOneRequired(Validators.required, ['clientId', 'username'])]); - break; - } - - securityGroup.updateValueAndValidity(); - }); - - ['caCert', 'privateKey', 'cert'].forEach(field => { - securityGroup.get(field).valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => this.cd.detectChanges()); - }); - } - - private observeStorageTypeChanges(): void { - const storageGroup = this.basicFormGroup.get('storage') as FormGroup; - - storageGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(type => { - this.removeAllStorageValidators(); - - switch (type) { - case StorageTypes.MEMORY: - this.addMemoryStorageValidators(storageGroup); - break; - case StorageTypes.FILE: - this.addFileStorageValidators(storageGroup); - break; - case StorageTypes.SQLITE: - this.addSqliteStorageValidators(storageGroup); - break; - } - }); - } - - private addAccessTokenValidators(group: FormGroup): void { - group.get('accessToken').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); - group.get('accessToken').updateValueAndValidity(); - } - - private addTlsPrivateKeyValidators(group: FormGroup): void { - ['caCert', 'privateKey', 'cert'].forEach(field => { - group.get(field).addValidators([Validators.required]); - group.get(field).updateValueAndValidity(); - }); - } - - private addTlsAccessTokenValidators(group: FormGroup): void { - this.addAccessTokenValidators(group); - group.get('caCert').addValidators([Validators.required]); - group.get('caCert').updateValueAndValidity(); - } - - private addMemoryStorageValidators(group: FormGroup): void { - group.get('read_records_count').addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); - group.get('max_records_count').addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); - group.get('read_records_count').updateValueAndValidity({ emitEvent: false }); - group.get('max_records_count').updateValueAndValidity({ emitEvent: false }); - } - - private addFileStorageValidators(group: FormGroup): void { - ['max_file_count', 'max_read_records_count', 'max_records_per_file'].forEach(field => { - group.get(field).addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); - group.get(field).updateValueAndValidity({ emitEvent: false }); - }); - } - - private addSqliteStorageValidators(group: FormGroup): void { - ['messages_ttl_check_in_hours', 'messages_ttl_in_days'].forEach(field => { - group.get(field).addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); - group.get(field).updateValueAndValidity({ emitEvent: false }); - }); - } - - private checkAndFetchCredentials(security: GatewayConfigSecurity): void { - if (security.type === SecurityTypes.TLS_PRIVATE_KEY) { - return; - } - - this.deviceService.getDeviceCredentials(this.device.id).pipe(takeUntil(this.destroy$)).subscribe(credentials => { - this.initialCredentialsUpdated.emit(credentials); - this.updateSecurityType(security, credentials); - this.updateCredentials(credentials, security); - }); - } - - private updateSecurityType(security, credentials: DeviceCredentials): void { - const isAccessToken = credentials.credentialsType === DeviceCredentialsType.ACCESS_TOKEN - || security.type === SecurityTypes.TLS_ACCESS_TOKEN; - const securityType = isAccessToken - ? (security.type === SecurityTypes.TLS_ACCESS_TOKEN ? SecurityTypes.TLS_ACCESS_TOKEN : SecurityTypes.ACCESS_TOKEN) - : (credentials.credentialsType === DeviceCredentialsType.MQTT_BASIC ? SecurityTypes.USERNAME_PASSWORD : null); - - if (securityType) { - this.basicFormGroup.get('thingsboard.security.type').setValue(securityType, { emitEvent: false }); - } - } - - private updateCredentials(credentials: DeviceCredentials, security: GatewayConfigSecurity): void { - switch (credentials.credentialsType) { - case DeviceCredentialsType.ACCESS_TOKEN: - this.updateAccessTokenCredentials(credentials, security); - break; - case DeviceCredentialsType.MQTT_BASIC: - this.updateMqttBasicCredentials(credentials); - break; - case DeviceCredentialsType.X509_CERTIFICATE: - break; - } - } - - private updateAccessTokenCredentials(credentials: DeviceCredentials, security: GatewayConfigSecurity): void { - this.basicFormGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId, { emitEvent: false }); - if (security.type === SecurityTypes.TLS_ACCESS_TOKEN) { - this.basicFormGroup.get('thingsboard.security.caCert').setValue(security.caCert, { emitEvent: false }); - } - } - - private updateMqttBasicCredentials(credentials: DeviceCredentials): void { - const parsedValue = JSON.parse(credentials.credentialsValue); - this.basicFormGroup.get('thingsboard.security.clientId').setValue(parsedValue.clientId, { emitEvent: false }); - this.basicFormGroup.get('thingsboard.security.username').setValue(parsedValue.userName, { emitEvent: false }); - this.basicFormGroup.get('thingsboard.security.password').setValue(parsedValue.password, { emitEvent: false }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.html deleted file mode 100644 index 7d8e0e78b3..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.html +++ /dev/null @@ -1,64 +0,0 @@ - -
-
- -
-

gateway.gateway-configuration

-
- - - {{ 'gateway.basic' | translate }} - - - {{ 'gateway.advanced' | translate }} - - - -
-
-
- - -
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.scss deleted file mode 100644 index 4d1d62ddad..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.scss +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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: flex; - flex-direction: column; - overflow: hidden; - - .page-header.mat-toolbar { - background: transparent; - color: rgba(0, 0, 0, .87) !important; - } - - .actions { - grid-row: 3; - padding: 8px 16px 8px 8px; - display: flex; - gap: 8px; - justify-content: flex-end; - position: absolute; - bottom: 0; - right: 0; - z-index: 1; - background: white; - width: 100%; - } - - .gateway-config-container { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - } - - .content-wrapper { - flex: 1; - } - - .toolbar-actions { - display: flex; - align-items: center; - } -} - -.dialog-toggle { - ::ng-deep.mat-button-toggle-button { - color: rgba(255, 255, 255, .75); - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts deleted file mode 100644 index 670fd57bd4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts +++ /dev/null @@ -1,396 +0,0 @@ -/// -/// 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 { ChangeDetectorRef, Component, Input, AfterViewInit, OnDestroy } from '@angular/core'; -import { - FormBuilder, - FormGroup, -} from '@angular/forms'; -import { EntityId } from '@shared/models/id/entity-id'; -import { MatDialogRef } from '@angular/material/dialog'; -import { AttributeService } from '@core/http/attribute.service'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { DeviceService } from '@core/http/device.service'; -import { Observable, of, Subject } from 'rxjs'; -import { mergeMap, switchMap, takeUntil } from 'rxjs/operators'; -import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; -import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { - GatewayLogLevel, - SecurityTypes, - ConfigurationModes, - LocalLogsConfigs, - LogSavingPeriod, Attribute -} from '../gateway-widget.models'; -import { deepTrim, isEqual } from '@core/utils'; -import { - GatewayConfigSecurity, - GatewayConfigValue, - GatewayGeneralConfig, - GatewayGRPCConfig, - GatewayLogsConfig, - GatewayStorageConfig, - LocalLogs, - LogAttribute, - LogConfig, -} from './models/gateway-configuration.models'; -import { DeviceId } from '@shared/models/id/device-id'; - -@Component({ - selector: 'tb-gateway-configuration', - templateUrl: './gateway-configuration.component.html', - styleUrls: ['./gateway-configuration.component.scss'] -}) -export class GatewayConfigurationComponent implements AfterViewInit, OnDestroy { - - @Input() device: EntityId; - - @Input() dialogRef: MatDialogRef; - - initialCredentials: DeviceCredentials; - gatewayConfigGroup: FormGroup; - ConfigurationModes = ConfigurationModes; - - private destroy$ = new Subject(); - private readonly gatewayConfigAttributeKeys = - ['general_configuration', 'grpc_configuration', 'logs_configuration', 'storage_configuration', 'RemoteLoggingLevel', 'mode']; - - constructor(private fb: FormBuilder, - private attributeService: AttributeService, - private deviceService: DeviceService, - private cd: ChangeDetectorRef - ) { - - this.gatewayConfigGroup = this.fb.group({ - basicConfig: [], - advancedConfig: [], - mode: [ConfigurationModes.BASIC], - }); - - this.observeAlignConfigs(); - } - - ngAfterViewInit(): void { - this.fetchConfigAttribute(this.device); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - saveConfig(): void { - const { mode, advancedConfig } = deepTrim(this.removeEmpty(this.gatewayConfigGroup.value)); - const value = { mode, ...advancedConfig as GatewayConfigValue }; - value.thingsboard.statistics.commands = Object.values(value.thingsboard.statistics.commands ?? []); - const attributes = this.generateAttributes(value); - - this.attributeService.saveEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, attributes).pipe( - switchMap(_ => this.updateCredentials(value.thingsboard.security)), - takeUntil(this.destroy$), - ).subscribe(() => { - if (this.dialogRef) { - this.dialogRef.close(); - } else { - this.gatewayConfigGroup.markAsPristine(); - this.cd.detectChanges(); - } - }); - } - - private observeAlignConfigs(): void { - this.gatewayConfigGroup.get('basicConfig').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { - const advancedControl = this.gatewayConfigGroup.get('advancedConfig'); - - if (!isEqual(advancedControl.value, value) && this.gatewayConfigGroup.get('mode').value === ConfigurationModes.BASIC) { - advancedControl.patchValue(value, {emitEvent: false}); - } - }); - - this.gatewayConfigGroup.get('advancedConfig').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { - const basicControl = this.gatewayConfigGroup.get('basicConfig'); - - if (!isEqual(basicControl.value, value) && this.gatewayConfigGroup.get('mode').value === ConfigurationModes.ADVANCED) { - basicControl.patchValue(value, {emitEvent: false}); - } - }); - } - - private generateAttributes(value: GatewayConfigValue): Attribute[] { - const attributes = []; - - const addAttribute = (key: string, val: unknown) => { - attributes.push({ key, value: val }); - }; - - const addTimestampedAttribute = (key: string, val: unknown) => { - val = {...val as Record, ts: new Date().getTime()}; - addAttribute(key, val); - }; - - addAttribute('RemoteLoggingLevel', value.logs?.remote?.enabled ? value.logs.remote.logLevel : GatewayLogLevel.NONE); - - delete value.connectors; - addAttribute('logs_configuration', this.generateLogsFile(value.logs)); - - addTimestampedAttribute('grpc_configuration', value.grpc); - addTimestampedAttribute('storage_configuration', value.storage); - addTimestampedAttribute('general_configuration', value.thingsboard); - - addAttribute('mode', value.mode); - - return attributes; - } - - private updateCredentials(securityConfig: GatewayConfigSecurity): Observable { - let newCredentials: Partial = {}; - - switch (securityConfig.type) { - case SecurityTypes.USERNAME_PASSWORD: - if (this.shouldUpdateCredentials(securityConfig)) { - newCredentials = this.generateMqttCredentials(securityConfig); - } - break; - - case SecurityTypes.ACCESS_TOKEN: - case SecurityTypes.TLS_ACCESS_TOKEN: - if (this.shouldUpdateAccessToken(securityConfig)) { - newCredentials = { - credentialsType: DeviceCredentialsType.ACCESS_TOKEN, - credentialsId: securityConfig.accessToken - }; - } - break; - } - - return Object.keys(newCredentials).length - ? this.deviceService.saveDeviceCredentials({ ...this.initialCredentials, ...newCredentials }) - : of(null); - } - - private shouldUpdateCredentials(securityConfig: GatewayConfigSecurity): boolean { - if (this.initialCredentials.credentialsType !== DeviceCredentialsType.MQTT_BASIC) { - return true; - } - const parsedCredentials = JSON.parse(this.initialCredentials.credentialsValue); - return !( - parsedCredentials.clientId === securityConfig.clientId && - parsedCredentials.userName === securityConfig.username && - parsedCredentials.password === securityConfig.password - ); - } - - private generateMqttCredentials(securityConfig: GatewayConfigSecurity): Partial { - const { clientId, username, password } = securityConfig; - - const credentialsValue = { - ...(clientId && { clientId }), - ...(username && { userName: username }), - ...(password && { password }), - }; - - return { - credentialsType: DeviceCredentialsType.MQTT_BASIC, - credentialsValue: JSON.stringify(credentialsValue) - }; - } - - private shouldUpdateAccessToken(securityConfig: GatewayConfigSecurity): boolean { - return this.initialCredentials.credentialsType !== DeviceCredentialsType.ACCESS_TOKEN || - this.initialCredentials.credentialsId !== securityConfig.accessToken; - } - - cancel(): void { - if (this.dialogRef) { - this.dialogRef.close(); - } - } - - private removeEmpty(obj: Record): Record { - return Object.fromEntries( - Object.entries(obj) - .filter(([_, v]) => v != null) - .map(([k, v]) => [k, v === Object(v) ? this.removeEmpty(v as Record) : v]) - ); - } - - private generateLogsFile(logsObj: GatewayLogsConfig): LogAttribute { - const logAttrObj = { - version: 1, - disable_existing_loggers: false, - formatters: { - LogFormatter: { - class: 'logging.Formatter', - format: logsObj.logFormat, - datefmt: logsObj.dateFormat, - } - }, - handlers: { - consoleHandler: { - class: 'logging.StreamHandler', - formatter: 'LogFormatter', - level: 0, - stream: 'ext://sys.stdout' - }, - databaseHandler: { - class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler', - formatter: 'LogFormatter', - filename: './logs/database.log', - backupCount: 1, - encoding: 'utf-8' - } - }, - loggers: { - database: { - handlers: ['databaseHandler', 'consoleHandler'], - level: 'DEBUG', - propagate: false - } - }, - root: { - level: 'ERROR', - handlers: [ - 'consoleHandler' - ] - }, - ts: new Date().getTime() - }; - - this.addLocalLoggers(logAttrObj, logsObj.local); - - return logAttrObj; - } - - private addLocalLoggers(logAttrObj: LogAttribute, localLogs: LocalLogs): void { - for (const key of Object.keys(localLogs)) { - logAttrObj.handlers[key + 'Handler'] = this.createHandlerObj(localLogs[key], key); - logAttrObj.loggers[key] = this.createLoggerObj(localLogs[key], key); - } - } - - private createHandlerObj(logObj: LogConfig, key: string) { - return { - class: 'thingsboard_gateway.tb_utility.tb_handler.TimedRotatingFileHandler', - formatter: 'LogFormatter', - filename: `${logObj.filePath}/${key}.log`, - backupCount: logObj.backupCount, - interval: logObj.savingTime, - when: logObj.savingPeriod, - encoding: 'utf-8' - }; - } - - private createLoggerObj(logObj: LogConfig, key: string) { - return { - handlers: [`${key}Handler`, 'consoleHandler'], - level: logObj.logLevel, - propagate: false - }; - } - - private fetchConfigAttribute(entityId: EntityId): void { - if (entityId.id === NULL_UUID) { - return; - } - - this.attributeService.getEntityAttributes(entityId, AttributeScope.CLIENT_SCOPE, - ) - .pipe( - mergeMap(attributes => attributes.length ? of(attributes) : this.attributeService.getEntityAttributes( - entityId, AttributeScope.SHARED_SCOPE, this.gatewayConfigAttributeKeys) - ), - takeUntil(this.destroy$) - ) - .subscribe(attributes => { - this.updateConfigs(attributes); - this.cd.detectChanges(); - }); - } - - private updateConfigs(attributes: AttributeData[]): void { - const formValue: GatewayConfigValue = { - thingsboard: {} as GatewayGeneralConfig, - grpc: {} as GatewayGRPCConfig, - logs: {} as GatewayLogsConfig, - storage: {} as GatewayStorageConfig, - mode: ConfigurationModes.BASIC - }; - - attributes.forEach(attr => { - switch (attr.key) { - case 'general_configuration': - formValue.thingsboard = attr.value; - this.updateFormControls(attr.value); - break; - case 'grpc_configuration': - formValue.grpc = attr.value; - break; - case 'logs_configuration': - formValue.logs = this.logsToObj(attr.value); - break; - case 'storage_configuration': - formValue.storage = attr.value; - break; - case 'mode': - formValue.mode = attr.value; - break; - case 'RemoteLoggingLevel': - formValue.logs = { - ...formValue.logs, - remote: { - enabled: attr.value !== GatewayLogLevel.NONE, - logLevel: attr.value - } - }; - } - }); - - this.gatewayConfigGroup.get('basicConfig').setValue(formValue, { emitEvent: false }); - this.gatewayConfigGroup.get('advancedConfig').setValue(formValue, { emitEvent: false }); - } - - private updateFormControls(thingsboard: GatewayGeneralConfig): void { - const { type, accessToken, ...securityConfig } = thingsboard.security ?? {}; - - this.initialCredentials = { - deviceId: this.device as DeviceId, - credentialsType: type as unknown as DeviceCredentialsType, - credentialsId: accessToken, - credentialsValue: JSON.stringify(securityConfig) - }; - } - - private logsToObj(logsConfig: LogAttribute): GatewayLogsConfig { - const { format: logFormat, datefmt: dateFormat } = logsConfig.formatters.LogFormatter; - - const localLogs = Object.keys(LocalLogsConfigs).reduce((acc, key) => { - const handler = logsConfig.handlers[`${key}Handler`] || {}; - const logger = logsConfig.loggers[key] || {}; - - acc[key] = { - logLevel: logger.level || GatewayLogLevel.INFO, - filePath: handler.filename?.split(`/${key}`)[0] || './logs', - backupCount: handler.backupCount || 7, - savingTime: handler.interval || 3, - savingPeriod: handler.when || LogSavingPeriod.days - }; - - return acc; - }, {}) as LocalLogs; - - return { local: localLogs, logFormat, dateFormat }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts deleted file mode 100644 index 4115c6c024..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts +++ /dev/null @@ -1,170 +0,0 @@ -/// -/// 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 { - ConfigurationModes, - GatewayConnector, - LocalLogsConfigs, - LogSavingPeriod, - SecurityTypes, - StorageTypes -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { GatewayLogLevel } from '@home/components/widget/lib/gateway/gateway-form.models'; - -export interface GatewayConfigValue { - mode: ConfigurationModes; - thingsboard: GatewayGeneralConfig; - storage: GatewayStorageConfig; - grpc: GatewayGRPCConfig; - connectors?: GatewayConnector[]; - logs: GatewayLogsConfig; -} - -export interface GatewayGRPCConfig { - enabled: boolean; - serverPort: number; - keepAliveTimeMs: number; - keepAliveTimeoutMs: number; - keepalivePermitWithoutCalls: boolean; - maxPingsWithoutData: number; - minTimeBetweenPingsMs: number; - minPingIntervalWithoutDataMs: number; -} - -export interface GatewayStorageConfig { - type: StorageTypes; - read_records_count?: number; - max_records_count?: number; - data_folder_path?: string; - max_file_count?: number; - max_read_records_count?: number; - max_records_per_file?: number; - data_file_path?: string; - messages_ttl_check_in_hours?: number; - messages_ttl_in_days?: number; -} - -export interface GatewayGeneralConfig { - host: string; - port: number; - remoteShell: boolean; - remoteConfiguration: boolean; - checkConnectorsConfigurationInSeconds: number; - statistics: { - enable: boolean; - statsSendPeriodInSeconds: number; - commands: GatewayConfigCommand[]; - }; - maxPayloadSizeBytes: number; - minPackSendDelayMS: number; - minPackSizeToSend: number; - handleDeviceRenaming: boolean; - checkingDeviceActivity: { - checkDeviceInactivity: boolean; - inactivityTimeoutSeconds?: number; - inactivityCheckPeriodSeconds?: number; - }; - security: GatewayConfigSecurity; - qos: number; -} - -export interface GatewayLogsConfig { - dateFormat: string; - logFormat: string; - type?: string; - remote?: { - enabled: boolean; - logLevel: GatewayLogLevel; - }; - local: LocalLogs; -} - -export interface GatewayConfigSecurity { - type: SecurityTypes; - accessToken?: string; - clientId?: string; - username?: string; - password?: string; - caCert?: string; - cert?: string; - privateKey?: string; -} - -export interface GatewayConfigCommand { - attributeOnGateway: string; - command: string; - timeout: number; -} - -export interface LogConfig { - logLevel: GatewayLogLevel; - filePath: string; - backupCount: number; - savingTime: number; - savingPeriod: LogSavingPeriod; -} - -export type LocalLogs = Record; - -interface LogFormatterConfig { - class: string; - format: string; - datefmt: string; -} - -interface StreamHandlerConfig { - class: string; - formatter: string; - level: string | number; - stream: string; -} - -interface FileHandlerConfig { - class: string; - formatter: string; - filename: string; - backupCount: number; - encoding: string; -} - -interface LoggerConfig { - handlers: string[]; - level: string; - propagate: boolean; -} - -interface RootConfig { - level: string; - handlers: string[]; -} - -export interface LogAttribute { - version: number; - disable_existing_loggers: boolean; - formatters: { - LogFormatter: LogFormatterConfig; - }; - handlers: { - consoleHandler: StreamHandlerConfig; - databaseHandler: FileHandlerConfig; - }; - loggers: { - database: LoggerConfig; - }; - root: RootConfig; - ts: number; -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.html deleted file mode 100644 index bbaf616471..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.html +++ /dev/null @@ -1,99 +0,0 @@ - -
-
device.device
-
-
-
gateway.device-info.entity-field
-
gateway.device-info.source
-
- gateway.device-info.expression -
-
-
-
-
gateway.device-info.name
-
- - - - {{ SourceTypeTranslationsMap.get(type) | translate }} - - - -
-
- - - - warning - -
-
-
-
-
-
-
gateway.device-info.profile-name
-
- - - - {{ SourceTypeTranslationsMap.get(type) | translate }} - - - -
-
- - - - warning - -
-
-
-
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.scss deleted file mode 100644 index 9dbaa52831..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.scss +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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; - - .tb-form-row { - &.bottom-same-padding { - padding-bottom: 16px; - } - - &.top-same-padding { - padding-top: 16px; - } - - .fixed-title-width { - width: 19%; - } - } - - .table-column { - width: 40%; - } - - .table-name-column { - width: 20%; - } - - .raw-name { - width: 19%; - } - - .raw-value-option { - max-width: 40%; - } - -} - -:host ::ng-deep { - .mat-mdc-form-field-icon-suffix { - display: flex; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts deleted file mode 100644 index 91432b7353..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts +++ /dev/null @@ -1,164 +0,0 @@ -/// -/// 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, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { - DeviceInfoType, - noLeadTrailSpacesRegex, - OPCUaSourceType, - SourceType, - SourceTypeTranslationsMap -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { coerceBoolean } from '@shared/decorators/coercion'; - -@Component({ - selector: 'tb-device-info-table', - templateUrl: './device-info-table.component.html', - styleUrls: ['./device-info-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DeviceInfoTableComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => DeviceInfoTableComponent), - multi: true - } - ] -}) -export class DeviceInfoTableComponent extends PageComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { - - SourceTypeTranslationsMap = SourceTypeTranslationsMap; - - DeviceInfoType = DeviceInfoType; - - @coerceBoolean() - @Input() - useSource = true; - - @coerceBoolean() - @Input() - required = false; - - @Input() - sourceTypes: Array = Object.values(SourceType); - - deviceInfoTypeValue: any; - - get deviceInfoType(): any { - return this.deviceInfoTypeValue; - } - - @Input() - set deviceInfoType(value: any) { - if (this.deviceInfoTypeValue !== value) { - this.deviceInfoTypeValue = value; - } - } - - mappingFormGroup: UntypedFormGroup; - - private destroy$ = new Subject(); - private propagateChange = (v: any) => {}; - - constructor(protected store: Store, - public translate: TranslateService, - public dialog: MatDialog, - private fb: FormBuilder) { - super(store); - } - - ngOnInit() { - this.mappingFormGroup = this.fb.group({ - deviceNameExpression: ['', this.required ? - [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)] : [Validators.pattern(noLeadTrailSpacesRegex)]] - }); - - if (this.useSource) { - this.mappingFormGroup.addControl('deviceNameExpressionSource', - this.fb.control(this.sourceTypes[0], [])); - } - - if (this.deviceInfoType === DeviceInfoType.FULL) { - if (this.useSource) { - this.mappingFormGroup.addControl('deviceProfileExpressionSource', - this.fb.control(this.sourceTypes[0], [])); - } - this.mappingFormGroup.addControl('deviceProfileExpression', - this.fb.control('', this.required ? - [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)] : [Validators.pattern(noLeadTrailSpacesRegex)])); - } - - this.mappingFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.updateView(value); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void {} - - writeValue(deviceInfo: any) { - this.mappingFormGroup.patchValue(deviceInfo, {emitEvent: false}); - } - - validate(): ValidationErrors | null { - return this.mappingFormGroup.valid ? null : { - mappingForm: { valid: false } - }; - } - - updateView(value: any) { - this.propagateChange(value); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html deleted file mode 100644 index 736d9c83c6..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html +++ /dev/null @@ -1,235 +0,0 @@ - -
-
-
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
-
-
-
- - - - - -
- {{ keyControl.get('key').value }} -
- {{ '-' }} -
-
{{ valueTitle(keyControl) }}
-
-
- -
-
-
gateway.platform-side
-
-
- {{ 'gateway.key' | translate }} -
-
- - - - warning - - -
-
-
-
-
gateway.connector-side
-
-
gateway.type
- - - -
- - - - {{ (valueTypes.get(keyControl.get('type').value)?.name || valueTypes.get(keyControl.get('type').value)) | translate }} - - - {{ 'gateway.raw' | translate }} - -
-
- - - - - - {{ valueTypes.get(valueType).name || valueTypes.get(valueType) | translate }} - - - - - - {{ 'gateway.raw' | translate }} - - -
-
-
-
-
- {{ 'gateway.value' | translate }} -
- - - - warning - -
-
-
-
-
-
-
-
-
gateway.key
-
- - - - warning - - -
-
-
-
gateway.value
- - - - warning - - -
-
-
-
-
- {{ 'gateway.method-name' | translate }} -
-
- - - - warning - - -
-
-
- - - -
- {{ 'gateway.arguments' | translate }}{{' (' + keyControl.get('arguments').value?.length + ')'}} -
-
-
- - - -
-
-
-
-
-
-
- -
-
-
- -
-
- -
- {{ noKeysText }} -
-
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.scss deleted file mode 100644 index 24498863c9..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.scss +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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 { - .tb-mapping-keys-panel { - width: 77vw; - max-width: 700px; - - .title-container { - max-width: 11vw; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap - } - - .key-panel { - height: 500px; - overflow: auto; - } - - tb-value-input { - width: 100%; - } - - .tb-form-panel { - .mat-mdc-icon-button { - width: 56px; - height: 56px; - padding: 16px; - color: rgba(0, 0, 0, 0.54); - } - } - - .see-example { - width: 32px; - height: 32px; - margin: 4px; - } - } -} - -:host ::ng-deep { - .mat-mdc-form-field-icon-suffix { - display: flex; - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts deleted file mode 100644 index de289dde2e..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts +++ /dev/null @@ -1,197 +0,0 @@ -/// -/// 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 { - Component, - EventEmitter, - Input, - OnInit, - Output -} from '@angular/core'; -import { - AbstractControl, - FormControl, - FormGroup, - UntypedFormArray, - UntypedFormBuilder, - Validators -} from '@angular/forms'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { TbPopoverComponent } from '@shared/components/popover.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { PageComponent } from '@shared/components/page.component'; -import { isDefinedAndNotNull } from '@core/utils'; -import { - MappingDataKey, - MappingKeysType, - MappingValueType, - mappingValueTypesMap, - noLeadTrailSpacesRegex, - OPCUaSourceType, - RpcMethodsMapping, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Component({ - selector: 'tb-mapping-data-keys-panel', - templateUrl: './mapping-data-keys-panel.component.html', - styleUrls: ['./mapping-data-keys-panel.component.scss'], - providers: [] -}) -export class MappingDataKeysPanelComponent extends PageComponent implements OnInit { - - @Input() - panelTitle: string; - - @Input() - addKeyTitle: string; - - @Input() - deleteKeyTitle: string; - - @Input() - noKeysText: string; - - @Input() - keys: Array | {[key: string]: any}; - - @Input() - keysType: MappingKeysType; - - @Input() - valueTypeKeys: Array = Object.values(MappingValueType); - - @Input() - valueTypeEnum = MappingValueType; - - @Input() - valueTypes: Map = mappingValueTypesMap; - - @Input() - @coerceBoolean() - rawData = false; - - @Input() - popover: TbPopoverComponent; - - @Output() - keysDataApplied = new EventEmitter | {[key: string]: unknown}>(); - - MappingKeysType = MappingKeysType; - - dataKeyType: DataKeyType; - - keysListFormArray: UntypedFormArray; - - errorText = ''; - - constructor(private fb: UntypedFormBuilder, - protected store: Store) { - super(store); - } - - ngOnInit(): void { - this.keysListFormArray = this.prepareKeysFormArray(this.keys); - } - - trackByKey(index: number, keyControl: AbstractControl): any { - return keyControl; - } - - addKey(): void { - let dataKeyFormGroup: FormGroup; - if (this.keysType === MappingKeysType.RPC_METHODS) { - dataKeyFormGroup = this.fb.group({ - method: ['', [Validators.required]], - arguments: [[], []] - }); - } else { - dataKeyFormGroup = this.fb.group({ - key: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }); - } - if (this.keysType !== MappingKeysType.CUSTOM && this.keysType !== MappingKeysType.RPC_METHODS) { - const controlValue = this.rawData ? 'raw' : this.valueTypeKeys[0]; - dataKeyFormGroup.addControl('type', this.fb.control(controlValue)); - } - this.keysListFormArray.push(dataKeyFormGroup); - } - - deleteKey($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - } - this.keysListFormArray.removeAt(index); - this.keysListFormArray.markAsDirty(); - } - - cancel(): void { - this.popover?.hide(); - } - - applyKeysData(): void { - let keys = this.keysListFormArray.value; - if (this.keysType === MappingKeysType.CUSTOM) { - keys = {}; - for (let key of this.keysListFormArray.value) { - keys[key.key] = key.value; - } - } - this.keysDataApplied.emit(keys); - } - - private prepareKeysFormArray(keys: Array | {[key: string]: any}): UntypedFormArray { - const keysControlGroups: Array = []; - if (keys) { - if (this.keysType === MappingKeysType.CUSTOM) { - keys = Object.keys(keys).map(key => { - return {key, value: keys[key], type: ''}; - }); - } - keys.forEach((keyData) => { - let dataKeyFormGroup: FormGroup; - if (this.keysType === MappingKeysType.RPC_METHODS) { - dataKeyFormGroup = this.fb.group({ - method: [keyData.method, [Validators.required]], - arguments: [[...keyData.arguments], []] - }); - } else { - const { key, value, type } = keyData; - dataKeyFormGroup = this.fb.group({ - key: [key, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - type: [type, []] - }); - } - keysControlGroups.push(dataKeyFormGroup); - }); - } - return this.fb.array(keysControlGroups); - } - - valueTitle(keyControl: FormControl): string { - const value = keyControl.get(this.keysType === MappingKeysType.RPC_METHODS ? 'method' : 'value').value; - if (isDefinedAndNotNull(value)) { - if (typeof value === 'object') { - return JSON.stringify(value); - } - return value; - } - return ''; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html deleted file mode 100644 index 7e1ab2bb62..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html +++ /dev/null @@ -1,125 +0,0 @@ - -
-
- -
-
- {{mappingTypeTranslationsMap.get(mappingType) | translate}} -
- - - -
-
- -
- - -   - - - -
-
-
- - - - {{ column.title | translate }} - - - {{ mapping[column.def] }} - - - - - - - - - - -
- -
-
- - - - -
-
-
- - -
-
- -
-
- - widget.no-data-found - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.scss deleted file mode 100644 index 707b5fa056..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.scss +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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 '../scss/constants'; - -:host { - width: 100%; - height: 100%; - display: block; - .tb-mapping-table { - .tb-mapping-table-content { - width: 100%; - height: 100%; - background: #fff; - overflow: hidden; - - &.tb-outlined-border { - box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); - border: solid 1px #e0e0e0; - border-radius: 4px; - } - - .mat-toolbar-tools{ - min-height: auto; - } - - .title-container{ - overflow: hidden; - } - - .tb-mapping-table-title { - padding-right: 20px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .table-container { - overflow: auto; - .mat-mdc-table { - table-layout: fixed; - min-width: 450px; - - .table-value-column { - padding: 0 12px; - width: 23%; - - &.request-column { - width: 38%; - } - } - } - } - - .ellipsis { - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - .no-data-found { - height: calc(100% - 120px); - } - - @media #{$mat-xs} { - .mat-toolbar { - height: auto; - min-height: 100px; - - .tb-mapping-table-title{ - padding-bottom: 5px; - width: 100%; - } - } - } -} - -:host ::ng-deep { - mat-cell.tb-value-cell { - cursor: pointer; - .mat-icon { - height: 24px; - width: 24px; - font-size: 24px; - color: #757575 - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts deleted file mode 100644 index bdf52beb99..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ /dev/null @@ -1,323 +0,0 @@ -/// -/// 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 { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - forwardRef, - Input, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; -import { DialogService } from '@core/services/dialog.service'; -import { Subject } from 'rxjs'; -import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormArray, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - AttributeUpdate, - ConnectorMapping, - ConnectRequest, - ConverterConnectorMapping, - ConvertorTypeTranslationsMap, - DeviceConnectorMapping, - DisconnectRequest, - MappingInfo, - MappingType, - MappingTypeTranslationsMap, - MappingValue, - RequestMappingValue, - RequestType, - RequestTypesTranslationsMap, - ServerSideRpc -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; -import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; - -@Component({ - selector: 'tb-mapping-table', - templateUrl: './mapping-table.component.html', - styleUrls: ['./mapping-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MappingTableComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MappingTableComponent), - multi: true - } - ], - standalone: true, - imports: [CommonModule, SharedModule] -}) -export class MappingTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { - - @Input() - @coerceBoolean() - required = false; - - @Input() - set mappingType(value: MappingType) { - if (this.mappingTypeValue !== value) { - this.mappingTypeValue = value; - } - } - - get mappingType(): MappingType { - return this.mappingTypeValue; - } - - @ViewChild('searchInput') searchInputField: ElementRef; - - mappingTypeTranslationsMap = MappingTypeTranslationsMap; - mappingTypeEnum = MappingType; - displayedColumns = []; - mappingColumns = []; - textSearchMode = false; - dataSource: MappingDatasource; - hidePageSize = false; - activeValue = false; - dirtyValue = false; - mappingTypeValue: MappingType; - mappingFormGroup: UntypedFormArray; - textSearch = this.fb.control('', {nonNullable: true}); - - private onChange: (value: string) => void = () => {}; - private onTouched: () => void = () => {}; - - private destroy$ = new Subject(); - - constructor(public translate: TranslateService, - public dialog: MatDialog, - private dialogService: DialogService, - private fb: FormBuilder) { - this.mappingFormGroup = this.fb.array([]); - this.dirtyValue = !this.activeValue; - this.dataSource = new MappingDatasource(); - } - - ngOnInit(): void { - this.setMappingColumns(); - this.displayedColumns.push(...this.mappingColumns.map(column => column.def), 'actions'); - this.mappingFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.updateTableData(value); - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - ngAfterViewInit(): void { - this.textSearch.valueChanges.pipe( - debounceTime(150), - distinctUntilChanged((prev, current) => (prev ?? '') === current.trim()), - takeUntil(this.destroy$) - ).subscribe((text) => { - const searchText = text.trim(); - this.updateTableData(this.mappingFormGroup.value, searchText.trim()); - }); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(connectorMappings: ConnectorMapping[]): void { - this.mappingFormGroup.clear(); - this.pushDataAsFormArrays(connectorMappings); - } - - validate(): ValidationErrors | null { - return !this.required || this.mappingFormGroup.controls.length ? null : { - mappingFormGroup: {valid: false} - }; - } - - enterFilterMode(): void { - this.textSearchMode = true; - setTimeout(() => { - this.searchInputField.nativeElement.focus(); - this.searchInputField.nativeElement.setSelectionRange(0, 0); - }, 10); - } - - exitFilterMode(): void { - this.updateTableData(this.mappingFormGroup.value); - this.textSearchMode = false; - this.textSearch.reset(); - } - - manageMapping($event: Event, index?: number): void { - if ($event) { - $event.stopPropagation(); - } - const value = isDefinedAndNotNull(index) ? this.mappingFormGroup.at(index).value : {}; - this.dialog.open(MappingDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - mappingType: this.mappingType, - value, - buttonTitle: isUndefinedOrNull(index) ? 'action.add' : 'action.apply' - } - }).afterClosed() - .pipe(take(1), takeUntil(this.destroy$)) - .subscribe(res => { - if (res) { - if (isDefinedAndNotNull(index)) { - this.mappingFormGroup.at(index).patchValue(res); - } else { - this.pushDataAsFormArrays([res]); - } - this.mappingFormGroup.markAsDirty(); - } - }); - } - - private updateTableData(value: ConnectorMapping[], textSearch?: string): void { - let tableValue = value.map(mappingValue => this.getMappingValue(mappingValue)); - if (textSearch) { - tableValue = tableValue.filter(mappingValue => - Object.values(mappingValue).some(val => - val.toString().toLowerCase().includes(textSearch.toLowerCase()) - ) - ); - } - this.dataSource.loadData(tableValue); - } - - deleteMapping($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - } - this.dialogService.confirm( - this.translate.instant('gateway.delete-mapping-title'), - '', - this.translate.instant('action.no'), - this.translate.instant('action.yes'), - true - ).subscribe((result) => { - if (result) { - this.mappingFormGroup.removeAt(index); - this.mappingFormGroup.markAsDirty(); - } - }); - } - - private pushDataAsFormArrays(data: ConnectorMapping[]): void { - if (data?.length) { - data.forEach((mapping: ConnectorMapping) => this.mappingFormGroup.push(this.fb.control(mapping))); - } - } - - private getMappingValue(value: ConnectorMapping): MappingValue { - switch (this.mappingType) { - case MappingType.DATA: - const converterType = ConvertorTypeTranslationsMap.get((value as ConverterConnectorMapping).converter?.type); - return { - topicFilter: (value as ConverterConnectorMapping).topicFilter, - QoS: (value as ConverterConnectorMapping).subscriptionQos, - converter: converterType ? this.translate.instant(converterType) : '' - }; - case MappingType.REQUESTS: - let details: string; - const requestValue = value as RequestMappingValue; - if (requestValue.requestType === RequestType.ATTRIBUTE_UPDATE) { - details = (requestValue.requestValue as AttributeUpdate).attributeFilter; - } else if (requestValue.requestType === RequestType.SERVER_SIDE_RPC) { - details = (requestValue.requestValue as ServerSideRpc).methodFilter; - } else { - details = (requestValue.requestValue as ConnectRequest | DisconnectRequest).topicFilter; - } - return { - requestType: (value as RequestMappingValue).requestType, - type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingValue).requestType)), - details - }; - case MappingType.OPCUA: - const deviceNamePattern = (value as DeviceConnectorMapping).deviceInfo?.deviceNameExpression; - const deviceProfileExpression = (value as DeviceConnectorMapping).deviceInfo?.deviceProfileExpression; - const { deviceNodePattern } = value as DeviceConnectorMapping; - return { - deviceNodePattern, - deviceNamePattern, - deviceProfileExpression - }; - default: - return {} as MappingValue; - } - } - - private setMappingColumns(): void { - switch (this.mappingType) { - case MappingType.DATA: - this.mappingColumns.push( - { def: 'topicFilter', title: 'gateway.topic-filter' }, - { def: 'QoS', title: 'gateway.mqtt-qos' }, - { def: 'converter', title: 'gateway.payload-type' } - ); - break; - case MappingType.REQUESTS: - this.mappingColumns.push( - { def: 'type', title: 'gateway.type' }, - { def: 'details', title: 'gateway.details' } - ); - break; - case MappingType.OPCUA: - this.mappingColumns.push( - { def: 'deviceNodePattern', title: 'gateway.device-node' }, - { def: 'deviceNamePattern', title: 'gateway.device-name' }, - { def: 'deviceProfileExpression', title: 'gateway.device-profile' } - ); - } - } -} - -export class MappingDatasource extends TbTableDatasource { - constructor() { - super(); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts deleted file mode 100644 index 66024c6a8c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// 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 { Directive } from '@angular/core'; -import { FormControl, FormGroup, ValidationErrors } from '@angular/forms'; -import { takeUntil } from 'rxjs/operators'; -import { isEqual } from '@core/utils'; -import { GatewayConnectorBasicConfigDirective } from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; -import { - ModbusBasicConfig, - ModbusBasicConfig_v3_5_2, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Directive() -export abstract class ModbusBasicConfigDirective - extends GatewayConnectorBasicConfigDirective { - - enableSlaveControl: FormControl = new FormControl(false); - - constructor() { - super(); - - this.enableSlaveControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enable => { - this.updateSlaveEnabling(enable); - this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); - }); - } - - override writeValue(basicConfig: OutputBasicConfig & ModbusBasicConfig): void { - super.writeValue(basicConfig); - this.onEnableSlaveControl(basicConfig); - } - - override validate(): ValidationErrors | null { - const { master, slave } = this.basicFormGroup.value; - const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); - if (!this.basicFormGroup.valid || isEmpty) { - return { basicFormGroup: { valid: false } }; - } - return null; - } - - protected override initBasicFormGroup(): FormGroup { - return this.fb.group({ - master: [], - slave: [], - }); - } - - private updateSlaveEnabling(isEnabled: boolean): void { - if (isEnabled) { - this.basicFormGroup.get('slave').enable({ emitEvent: false }); - } else { - this.basicFormGroup.get('slave').disable({ emitEvent: false }); - } - } - - private onEnableSlaveControl(basicConfig: ModbusBasicConfig): void { - this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html deleted file mode 100644 index 76098288b8..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - -
-
{{ 'gateway.hints.modbus-server' | translate }}
-
- - - {{ 'gateway.enable' | translate }} - - -
-
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss deleted file mode 100644 index 3b7e7288c8..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -/** - * 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 { - height: 100%; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts deleted file mode 100644 index dceffd3f7d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// 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 } from '@angular/core'; -import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { - ModbusBasicConfig_v3_5_2, - ModbusMasterConfig, - ModbusSlave -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; -import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; -import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; -import { - ModbusBasicConfigDirective -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; - -@Component({ - selector: 'tb-modbus-basic-config', - templateUrl: './modbus-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusSlaveConfigComponent, - ModbusMasterTableComponent, - EllipsisChipListDirective, - ], - styleUrls: ['./modbus-basic-config.component.scss'], -}) -export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { - - isLegacy = false; - - protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { - return { - master: master?.slaves ? master : { slaves: [] } as ModbusMasterConfig, - slave: slave ?? {} as ModbusSlave, - }; - } - - protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { - return { - master: value.master, - slave: value.slave, - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts deleted file mode 100644 index c10214d57b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// -/// 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 } from '@angular/core'; -import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { - LegacySlaveConfig, - ModbusBasicConfig, - ModbusLegacyBasicConfig, - ModbusLegacySlave, - ModbusMasterConfig, - ModbusSlave -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; -import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; -import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; -import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; -import { - ModbusBasicConfigDirective -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; - -@Component({ - selector: 'tb-modbus-legacy-basic-config', - templateUrl: './modbus-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusSlaveConfigComponent, - ModbusMasterTableComponent, - EllipsisChipListDirective, - ], - styleUrls: ['./modbus-basic-config.component.scss'], -}) -export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { - - isLegacy = true; - - protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig { - return { - master: config.master?.slaves ? config.master : { slaves: [] } as ModbusMasterConfig, - slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave as ModbusLegacySlave) : {}, - } as ModbusBasicConfig; - } - - protected override getMappedValue(value: ModbusBasicConfig): ModbusLegacyBasicConfig { - return { - master: value.master as ModbusMasterConfig, - slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave as ModbusSlave) : {} as ModbusLegacySlave, - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html deleted file mode 100644 index 890c0428f0..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ /dev/null @@ -1,270 +0,0 @@ - -
-
-
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
-
-
-
- - - - -
- {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }} -
- -
-
{{ 'gateway.key' | translate }}: - {{ keyControl.get('tag').value }} -
-
{{ 'gateway.address' | translate }}: - {{ keyControl.get('address').value }} -
-
{{ 'gateway.type' | translate }}: - {{ keyControl.get('type').value }} -
-
-
-
-
- -
- {{ 'gateway.hints.modbus.data-keys' | translate }} -
-
-
-
-
gateway.platform-side
-
-
- gateway.key -
-
- - - - warning - - -
-
-
-
-
gateway.connector-side
-
-
- gateway.type -
-
- - - {{ type }} - - -
-
-
-
gateway.function-code
-
- - - - {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }} - - - -
-
-
-
gateway.objects-count
-
- - - - warning - - -
-
-
-
gateway.address
-
- - - - warning - - -
-
-
- - - - - - {{ 'gateway.modifier' | translate }} - - - - -
-
-
gateway.type
-
- - - -
- - {{ ModifierTypesMap.get(keyControl.get('modifierType').value)?.name | translate}} -
-
- - - - {{ ModifierTypesMap.get(modifierType).name | translate }} - -
-
-
-
-
-
-
gateway.value
- - - - warning - - -
-
-
-
-
gateway.value
-
- - - - warning - - -
-
- -
-
-
-
-
- -
-
-
- -
-
- -
- {{ noKeysText }} -
-
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss deleted file mode 100644 index d242fd4e71..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss +++ /dev/null @@ -1,45 +0,0 @@ -/** - * 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 { - .tb-modbus-keys-panel { - width: 77vw; - max-width: 700px; - - .title-container { - width: 180px; - } - - .key-label { - font-weight: 400; - } - - .key-panel { - height: 500px; - overflow: auto; - } - - .tb-form-panel { - .mat-mdc-icon-button { - width: 56px; - height: 56px; - padding: 16px; - color: rgba(0, 0, 0, 0.54); - } - } - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts deleted file mode 100644 index fcf3d49cf0..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts +++ /dev/null @@ -1,306 +0,0 @@ -/// -/// 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 { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { - AbstractControl, - FormArray, - FormControl, - FormGroup, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, - Validators -} from '@angular/forms'; -import { TbPopoverComponent } from '@shared/components/popover.component'; -import { - ModbusDataType, - ModbusEditableDataTypes, - ModbusFormValue, - ModbusFunctionCodeTranslationsMap, - ModbusObjectCountByDataType, - ModbusValue, - ModbusValueKey, - ModifierType, - ModifierTypesMap, - noLeadTrailSpacesRegex, - nonZeroFloat, - ReportStrategyDefaultValue, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { GatewayHelpLinkPipe } from '@home/components/widget/lib/gateway/pipes/gateway-help-link.pipe'; -import { generateSecret } from '@core/utils'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { - ReportStrategyComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; - -@Component({ - selector: 'tb-modbus-data-keys-panel', - templateUrl: './modbus-data-keys-panel.component.html', - styleUrls: ['./modbus-data-keys-panel.component.scss'], - standalone: true, - imports: [ - CommonModule, - SharedModule, - GatewayHelpLinkPipe, - ReportStrategyComponent, - ] -}) -export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { - - @coerceBoolean() - @Input() isMaster = false; - @coerceBoolean() - @Input() hideNewFields = false; - @Input() panelTitle: string; - @Input() addKeyTitle: string; - @Input() deleteKeyTitle: string; - @Input() noKeysText: string; - @Input() keysType: ModbusValueKey; - @Input() values: ModbusValue[]; - @Input() popover: TbPopoverComponent; - - @Output() keysDataApplied = new EventEmitter>(); - - keysListFormArray: FormArray; - modbusDataTypes = Object.values(ModbusDataType); - modifierTypes: ModifierType[] = Object.values(ModifierType); - withFunctionCode = true; - withReportStrategy = true; - - enableModifiersControlMap = new Map>(); - showModifiersMap = new Map(); - functionCodesMap = new Map(); - defaultFunctionCodes = []; - - readonly ModbusEditableDataTypes = ModbusEditableDataTypes; - readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap; - readonly ModifierTypesMap = ModifierTypesMap; - readonly ReportStrategyDefaultValue = ReportStrategyDefaultValue; - - private destroy$ = new Subject(); - - private readonly defaultReadFunctionCodes = [3, 4]; - private readonly bitsReadFunctionCodes = [1, 2]; - private readonly defaultWriteFunctionCodes = [6, 16]; - private readonly bitsWriteFunctionCodes = [5, 15]; - - constructor(private fb: UntypedFormBuilder) {} - - ngOnInit(): void { - this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TIMESERIES); - this.withReportStrategy = !this.isMaster - && (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES) - && !this.hideNewFields; - this.keysListFormArray = this.prepareKeysFormArray(this.values); - this.defaultFunctionCodes = this.getDefaultFunctionCodes(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - trackByControlId(_: number, keyControl: AbstractControl): string { - return keyControl.value.id; - } - - addKey(): void { - const id = generateSecret(5); - const dataKeyFormGroup = this.fb.group({ - tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [{value: '', disabled: !this.isMaster}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - type: [ModbusDataType.BYTES, [Validators.required]], - address: [null, [Validators.required]], - objectsCount: [1, [Validators.required]], - functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]], - reportStrategy: [{ value: null, disabled: !this.withReportStrategy }], - modifierType: [{ value: ModifierType.MULTIPLIER, disabled: true }], - modifierValue: [{ value: 1, disabled: true }, [Validators.pattern(nonZeroFloat)]], - id: [{value: id, disabled: true}], - }); - this.showModifiersMap.set(id, false); - this.enableModifiersControlMap.set(id, this.fb.control(false)); - this.observeKeyDataType(dataKeyFormGroup); - this.observeEnableModifier(dataKeyFormGroup); - - this.keysListFormArray.push(dataKeyFormGroup); - } - - deleteKey($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - } - this.keysListFormArray.removeAt(index); - this.keysListFormArray.markAsDirty(); - } - - cancel(): void { - this.popover.hide(); - } - - applyKeysData(): void { - this.keysDataApplied.emit(this.getFormValue()); - } - - private getFormValue(): ModbusValue[] { - return this.mapKeysWithModifier( - this.withReportStrategy - ? this.cleanUpEmptyStrategies(this.keysListFormArray.value) - : this.keysListFormArray.value - ); - } - - private cleanUpEmptyStrategies(values: ModbusValue[]): ModbusValue[] { - return values.map((key) => { - const { reportStrategy, ...updatedKey } = key; - return !reportStrategy ? updatedKey : key; - }); - } - - private mapKeysWithModifier(values: Array): Array { - return values.map((keyData, i) => { - if (this.showModifiersMap.get(this.keysListFormArray.controls[i].get('id').value)) { - const { modifierType, modifierValue, ...value } = keyData; - return modifierType ? { ...value, [modifierType]: modifierValue } : value; - } - return keyData; - }); - } - - private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray { - const keysControlGroups: Array = []; - - if (values) { - values.forEach(value => { - const dataKeyFormGroup = this.createDataKeyFormGroup(value); - this.observeKeyDataType(dataKeyFormGroup); - this.observeEnableModifier(dataKeyFormGroup); - this.functionCodesMap.set(dataKeyFormGroup.get('id').value, this.getFunctionCodes(value.type)); - - keysControlGroups.push(dataKeyFormGroup); - }); - } - - return this.fb.array(keysControlGroups); - } - - private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup { - const { tag, value, type, address, objectsCount, functionCode, multiplier, divider, reportStrategy } = modbusValue; - const id = generateSecret(5); - - const showModifier = this.shouldShowModifier(type); - this.showModifiersMap.set(id, showModifier); - this.enableModifiersControlMap.set(id, this.fb.control((multiplier || divider) && showModifier)); - - return this.fb.group({ - tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [{ value, disabled: !this.isMaster }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - type: [type, [Validators.required]], - address: [address, [Validators.required]], - objectsCount: [objectsCount, [Validators.required]], - functionCode: [{ value: functionCode, disabled: !this.withFunctionCode }, [Validators.required]], - modifierType: [{ - value: divider ? ModifierType.DIVIDER : ModifierType.MULTIPLIER, - disabled: !this.enableModifiersControlMap.get(id).value - }], - modifierValue: [ - { value: multiplier ?? divider ?? 1, disabled: !this.enableModifiersControlMap.get(id).value }, - [Validators.pattern(nonZeroFloat)] - ], - id: [{ value: id, disabled: true }], - reportStrategy: [{ value: reportStrategy, disabled: !this.withReportStrategy }], - }); - } - - private shouldShowModifier(type: ModbusDataType): boolean { - return !this.isMaster - && (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES) - && (!this.ModbusEditableDataTypes.includes(type)); - } - - private observeKeyDataType(keyFormGroup: FormGroup): void { - keyFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => { - if (!this.ModbusEditableDataTypes.includes(dataType)) { - keyFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false}); - } - const withModifier = this.shouldShowModifier(dataType); - this.showModifiersMap.set(keyFormGroup.get('id').value, withModifier); - this.updateFunctionCodes(keyFormGroup, dataType); - }); - } - - private observeEnableModifier(keyFormGroup: FormGroup): void { - this.enableModifiersControlMap.get(keyFormGroup.get('id').value).valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(showModifier => this.toggleModifierControls(keyFormGroup, showModifier)); - } - - private toggleModifierControls(keyFormGroup: FormGroup, enable: boolean): void { - const modifierTypeControl = keyFormGroup.get('modifierType'); - const modifierValueControl = keyFormGroup.get('modifierValue'); - - if (enable) { - modifierTypeControl.enable(); - modifierValueControl.enable(); - } else { - modifierTypeControl.disable(); - modifierValueControl.disable(); - } - } - - private updateFunctionCodes(keyFormGroup: FormGroup, dataType: ModbusDataType): void { - const functionCodes = this.getFunctionCodes(dataType); - this.functionCodesMap.set(keyFormGroup.get('id').value, functionCodes); - if (!functionCodes.includes(keyFormGroup.get('functionCode').value)) { - keyFormGroup.get('functionCode').patchValue(functionCodes[0], {emitEvent: false}); - } - } - - private getFunctionCodes(dataType: ModbusDataType): number[] { - const writeFunctionCodes = [ - ...(dataType === ModbusDataType.BITS ? this.bitsWriteFunctionCodes : []), ...this.defaultWriteFunctionCodes - ]; - - if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) { - return writeFunctionCodes.sort((a, b) => a - b); - } - - const functionCodes = [...this.defaultReadFunctionCodes]; - if (dataType === ModbusDataType.BITS) { - functionCodes.push(...this.bitsReadFunctionCodes); - } - if (this.keysType === ModbusValueKey.RPC_REQUESTS) { - functionCodes.push(...writeFunctionCodes); - } - - return functionCodes.sort((a, b) => a - b); - } - - private getDefaultFunctionCodes(): number[] { - if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) { - return this.defaultWriteFunctionCodes; - } - if (this.keysType === ModbusValueKey.RPC_REQUESTS) { - return [...this.defaultReadFunctionCodes, ...this.defaultWriteFunctionCodes]; - } - return this.defaultReadFunctionCodes; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html deleted file mode 100644 index 0bdfc9ab87..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html +++ /dev/null @@ -1,150 +0,0 @@ - -
-
-
{{ 'gateway.hints.modbus-master' | translate }}
-
-
- -
-
- {{ 'gateway.servers-slaves' | translate}} -
- - - -
-
- -
- - -   - - - -
-
-
- - - -
{{ 'gateway.device-name' | translate }}
-
- -
{{ slave['deviceName'] }}
-
-
- - - {{ 'gateway.info' | translate }} - - -
{{ slave['host'] ?? slave['port'] }}
-
-
- - - {{ 'gateway.unit-id' | translate }} - - -
{{ slave['unitId'] }}
-
-
- - -
{{ 'gateway.type' | translate }}
-
- - {{ ModbusProtocolLabelsMap.get(slave['type']) }} - -
- - - - - - - - -
- -
-
- - - - -
-
-
- - -
-
- -
-
- - widget.no-data-found - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss deleted file mode 100644 index e9a5d3ebcd..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 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 '../scss/constants'; - -:host { - width: 100%; - height: 100%; - display: block; - - .tb-master-table { - - .tb-master-table-content { - width: 100%; - height: 100%; - background: #fff; - overflow: hidden; - - .mat-toolbar-tools{ - min-height: auto; - } - - .title-container{ - overflow: hidden; - } - - .tb-master-table-title { - padding-right: 20px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .table-container { - overflow: auto; - - .mat-mdc-table { - table-layout: fixed; - min-width: 450px; - - .table-value-column { - padding: 0 12px; - width: 38%; - } - } - } - } - } - - .no-data-found { - height: calc(100% - 120px); - } - - @media #{$mat-xs} { - .mat-toolbar { - height: auto; - min-height: 100px; - - .tb-master-table-title{ - padding-bottom: 5px; - width: 100%; - } - } - } -} - -:host ::ng-deep { - mat-cell.tb-value-cell { - cursor: pointer; - - .mat-icon { - height: 24px; - width: 24px; - font-size: 24px; - color: #757575 - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts deleted file mode 100644 index 71bbc76c75..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts +++ /dev/null @@ -1,245 +0,0 @@ -/// -/// 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 { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - forwardRef, - Input, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { DialogService } from '@core/services/dialog.service'; -import { Subject } from 'rxjs'; -import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; -import { - ControlValueAccessor, - FormArray, - FormBuilder, - NG_VALUE_ACCESSOR, - UntypedFormGroup, -} from '@angular/forms'; -import { - LegacySlaveConfig, - ModbusMasterConfig, - ModbusProtocolLabelsMap, - ModbusSlaveInfo, - ModbusValues, - SlaveConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { isDefinedAndNotNull } from '@core/utils'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component'; -import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { - ModbusLegacySlaveDialogComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component'; - -@Component({ - selector: 'tb-modbus-master-table', - templateUrl: './modbus-master-table.component.html', - styleUrls: ['./modbus-master-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusMasterTableComponent), - multi: true - }, - ], - standalone: true, - imports: [CommonModule, SharedModule] -}) -export class ModbusMasterTableComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy { - - @ViewChild('searchInput') searchInputField: ElementRef; - - @coerceBoolean() - @Input() isLegacy = false; - - textSearchMode = false; - dataSource: SlavesDatasource; - masterFormGroup: UntypedFormGroup; - textSearch = this.fb.control('', {nonNullable: true}); - - readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; - - private onChange: (value: ModbusMasterConfig) => void = () => {}; - private onTouched: () => void = () => {}; - - private destroy$ = new Subject(); - - constructor( - public translate: TranslateService, - public dialog: MatDialog, - private dialogService: DialogService, - private fb: FormBuilder, - private cdr: ChangeDetectorRef, - ) { - this.masterFormGroup = this.fb.group({ slaves: this.fb.array([]) }); - this.dataSource = new SlavesDatasource(); - } - - get slaves(): FormArray { - return this.masterFormGroup.get('slaves') as FormArray; - } - - ngOnInit(): void { - this.masterFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.updateTableData(value.slaves); - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - ngAfterViewInit(): void { - this.textSearch.valueChanges.pipe( - debounceTime(150), - distinctUntilChanged((prev, current) => (prev ?? '') === current.trim()), - takeUntil(this.destroy$) - ).subscribe(text => this.updateTableData(this.slaves.value, text.trim())); - } - - registerOnChange(fn: (value: ModbusMasterConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(master: ModbusMasterConfig): void { - this.slaves.clear(); - this.pushDataAsFormArrays(master.slaves); - } - - enterFilterMode(): void { - this.textSearchMode = true; - this.cdr.detectChanges(); - const searchInput = this.searchInputField.nativeElement; - searchInput.focus(); - searchInput.setSelectionRange(0, 0); - } - - exitFilterMode(): void { - this.updateTableData(this.slaves.value); - this.textSearchMode = false; - this.textSearch.reset(); - } - - manageSlave($event: Event, index?: number): void { - if ($event) { - $event.stopPropagation(); - } - const withIndex = isDefinedAndNotNull(index); - const value = withIndex ? this.slaves.at(index).value : {}; - this.getSlaveDialog(value, withIndex ? 'action.apply' : 'action.add').afterClosed() - .pipe(take(1), takeUntil(this.destroy$)) - .subscribe(res => { - if (res) { - if (withIndex) { - this.slaves.at(index).patchValue(res); - } else { - this.slaves.push(this.fb.control(res)); - } - this.masterFormGroup.markAsDirty(); - } - }); - } - - private getSlaveDialog( - value: LegacySlaveConfig | SlaveConfig, - buttonTitle: string - ): MatDialogRef { - if (this.isLegacy) { - return this.dialog.open, ModbusValues> - (ModbusLegacySlaveDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - value: value as LegacySlaveConfig, - hideNewFields: true, - buttonTitle - } - }); - } - return this.dialog.open(ModbusSlaveDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - value: value as SlaveConfig, - buttonTitle, - hideNewFields: false, - } - }); - } - - deleteSlave($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - } - this.dialogService.confirm( - this.translate.instant('gateway.delete-slave-title'), - '', - this.translate.instant('action.no'), - this.translate.instant('action.yes'), - true - ).pipe(take(1), takeUntil(this.destroy$)).subscribe((result) => { - if (result) { - this.slaves.removeAt(index); - this.masterFormGroup.markAsDirty(); - } - }); - } - - private updateTableData(data: SlaveConfig[], textSearch?: string): void { - if (textSearch) { - data = data.filter(item => - Object.values(item).some(value => - value.toString().toLowerCase().includes(textSearch.toLowerCase()) - ) - ); - } - this.dataSource.loadData(data); - } - - private pushDataAsFormArrays(slaves: SlaveConfig[]): void { - if (slaves?.length) { - slaves.forEach((slave: SlaveConfig) => this.slaves.push(this.fb.control(slave))); - } - } -} - -export class SlavesDatasource extends TbTableDatasource { - constructor() { - super(); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html deleted file mode 100644 index 3fc3e33ad0..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html +++ /dev/null @@ -1,66 +0,0 @@ - -
-
{{ 'gateway.hints.path-in-os' | translate }}
-
-
- gateway.client-cert-path -
-
- - - -
-
-
-
- gateway.private-key-path -
-
- - - -
-
-
-
gateway.password
-
- - -
- -
-
-
-
-
-
gateway.server-hostname
-
- - - -
-
-
- - - {{ 'gateway.request-client-certificate' | translate }} - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts deleted file mode 100644 index bc40727b55..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts +++ /dev/null @@ -1,163 +0,0 @@ -/// -/// 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, - ChangeDetectorRef, - Component, - forwardRef, - Input, - OnChanges, - OnDestroy -} from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { - ModbusSecurity, - noLeadTrailSpacesRegex, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { coerceBoolean } from '@shared/decorators/coercion'; - -@Component({ - selector: 'tb-modbus-security-config', - templateUrl: './modbus-security-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusSecurityConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusSecurityConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ] -}) -export class ModbusSecurityConfigComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy { - - @coerceBoolean() - @Input() isMaster = false; - - securityConfigFormGroup: UntypedFormGroup; - - private disabled = false; - - private onChange: (value: ModbusSecurity) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) { - this.securityConfigFormGroup = this.fb.group({ - certfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - keyfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - password: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - server_hostname: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - reqclicert: [{value: false, disabled: true}], - }); - - this.observeValueChanges(); - } - - ngOnChanges(): void { - this.updateMasterEnabling(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: ModbusSecurity) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; - if (this.disabled) { - this.securityConfigFormGroup.disable({emitEvent: false}); - } else { - this.securityConfigFormGroup.enable({emitEvent: false}); - } - this.updateMasterEnabling(); - this.cdr.markForCheck(); - } - - validate(): ValidationErrors | null { - return this.securityConfigFormGroup.valid ? null : { - securityConfigFormGroup: { valid: false } - }; - } - - writeValue(securityConfig: ModbusSecurity): void { - const { certfile, password, keyfile, server_hostname } = securityConfig; - const securityState = { - certfile: certfile ?? '', - password: password ?? '', - keyfile: keyfile ?? '', - server_hostname: server_hostname ?? '', - reqclicert: !!securityConfig.reqclicert, - }; - - this.securityConfigFormGroup.reset(securityState, {emitEvent: false}); - } - - private updateMasterEnabling(): void { - if (this.isMaster) { - if (!this.disabled) { - this.securityConfigFormGroup.get('reqclicert').enable({emitEvent: false}); - } - this.securityConfigFormGroup.get('server_hostname').disable({emitEvent: false}); - } else { - if (!this.disabled) { - this.securityConfigFormGroup.get('server_hostname').enable({emitEvent: false}); - } - this.securityConfigFormGroup.get('reqclicert').disable({emitEvent: false}); - } - } - - private observeValueChanges(): void { - this.securityConfigFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value: ModbusSecurity) => { - this.onChange(value); - this.onTouched(); - }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html deleted file mode 100644 index 7008f3c70a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ /dev/null @@ -1,274 +0,0 @@ - -
-
-
-
gateway.server-slave-config
- - {{ ModbusProtocolLabelsMap.get(type) }} - -
-
-
-
gateway.host
-
- - - - warning - - -
-
-
-
gateway.port
-
- - - - warning - - -
-
- -
-
gateway.port
-
- - - - warning - - -
-
-
-
-
- gateway.method -
-
- - - {{ ModbusMethodLabelsMap.get(method) }} - - -
-
-
-
-
gateway.unit-id
-
- - - - warning - - -
-
-
-
gateway.device-name
-
- - - - warning - - -
-
-
-
gateway.device-profile
-
- - - - warning - - -
-
-
-
- - gateway.poll-period - -
-
- - - -
-
-
-
gateway.baudrate
-
- - - {{ rate }} - - -
-
-
- - - {{ 'gateway.send-data-to-platform' | translate }} - - -
-
- - - -
gateway.advanced-connection-settings
-
-
-
-
-
gateway.byte-order
-
- - - {{ order }} - - -
-
-
-
gateway.word-order
-
- - - {{ order }} - - -
-
-
- - - - - - {{ 'gateway.tls-connection' | translate }} - - - - - - -
- -
-
gateway.vendor-name
-
- - - -
-
-
-
gateway.product-code
-
- - - -
-
-
-
gateway.vendor-url
-
- - - -
-
-
-
gateway.product-name
-
- - - -
-
-
-
gateway.model-name
-
- - - -
-
-
-
-
-
-
-
gateway.values
- -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts deleted file mode 100644 index f4dfa57f96..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ /dev/null @@ -1,283 +0,0 @@ -/// -/// 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 { - ControlValueAccessor, - FormBuilder, - FormControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators, -} from '@angular/forms'; -import { - ModbusBaudrates, - ModbusMethodLabelsMap, - ModbusMethodType, - ModbusOrderType, - ModbusProtocolLabelsMap, - ModbusProtocolType, - ModbusRegisterValues, - ModbusSerialMethodType, - ModbusSlave, - noLeadTrailSpacesRegex, - PortLimits, - SlaveConfig, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; -import { ModbusValuesComponent, } from '../modbus-values/modbus-values.component'; -import { isEqual } from '@core/utils'; - -@Component({ - selector: 'tb-modbus-slave-config', - templateUrl: './modbus-slave-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusSlaveConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusSlaveConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusValuesComponent, - ModbusSecurityConfigComponent, - GatewayPortTooltipPipe, - ], -}) -export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - - slaveConfigFormGroup: UntypedFormGroup; - showSecurityControl: FormControl; - ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; - ModbusMethodLabelsMap = ModbusMethodLabelsMap; - portLimits = PortLimits; - - readonly modbusProtocolTypes = Object.values(ModbusProtocolType); - readonly modbusMethodTypes = Object.values(ModbusMethodType); - readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); - readonly modbusOrderType = Object.values(ModbusOrderType); - readonly ModbusProtocolType = ModbusProtocolType; - readonly modbusBaudrates = ModbusBaudrates; - - private isSlaveEnabled = false; - private readonly serialSpecificControlKeys = ['serialPort', 'baudrate']; - private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; - - private onChange: (value: SlaveConfig) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.showSecurityControl = this.fb.control(false); - this.slaveConfigFormGroup = this.fb.group({ - type: [ModbusProtocolType.TCP], - host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], - serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - method: [ModbusMethodType.SOCKET], - unitId: [null, [Validators.required]], - baudrate: [this.modbusBaudrates[0]], - deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - pollPeriod: [5000, [Validators.required]], - sendDataToThingsBoard: [false], - byteOrder:[ModbusOrderType.BIG], - wordOrder: [ModbusOrderType.BIG], - security: [], - identity: this.fb.group({ - vendorName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - productCode: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - vendorUrl: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - productName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - modelName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - }), - values: [], - }); - - this.observeValueChanges(); - this.observeTypeChange(); - this.observeShowSecurity(); - } - - get protocolType(): ModbusProtocolType { - return this.slaveConfigFormGroup.get('type').value; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: SlaveConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.slaveConfigFormGroup.valid ? null : { - slaveConfigFormGroup: { valid: false } - }; - } - - writeValue(slaveConfig: ModbusSlave): void { - this.showSecurityControl.patchValue(!!slaveConfig.security && !isEqual(slaveConfig.security, {})); - this.updateSlaveConfig(slaveConfig); - } - - setDisabledState(isDisabled: boolean): void { - this.isSlaveEnabled = !isDisabled; - this.updateFormEnableState(); - } - - private observeValueChanges(): void { - this.slaveConfigFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value: SlaveConfig) => { - if (value.type === ModbusProtocolType.Serial) { - value.port = value.serialPort; - delete value.serialPort; - } - this.onChange(value); - this.onTouched(); - }); - } - - private observeTypeChange(): void { - this.slaveConfigFormGroup.get('type').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(type => { - this.updateFormEnableState(); - this.updateMethodType(type); - }); - } - - private updateMethodType(type: ModbusProtocolType): void { - if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { - this.slaveConfigFormGroup.get('method').patchValue( - type === ModbusProtocolType.Serial - ? ModbusSerialMethodType.ASCII - : ModbusMethodType.SOCKET, - {emitEvent: false} - ); - } - } - - private updateFormEnableState(): void { - if (this.isSlaveEnabled) { - this.slaveConfigFormGroup.enable({emitEvent: false}); - this.showSecurityControl.enable({emitEvent: false}); - } else { - this.slaveConfigFormGroup.disable({emitEvent: false}); - this.showSecurityControl.disable({emitEvent: false}); - } - this.updateEnablingByProtocol(); - this.updateSecurityEnable(this.showSecurityControl.value); - } - - private observeShowSecurity(): void { - this.showSecurityControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => this.updateSecurityEnable(value)); - } - - private updateSecurityEnable(securityEnabled: boolean): void { - if (securityEnabled && this.isSlaveEnabled && this.protocolType !== ModbusProtocolType.Serial) { - this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); - } else { - this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); - } - } - - private updateEnablingByProtocol(): void { - const isSerial = this.protocolType === ModbusProtocolType.Serial; - const enableKeys = isSerial ? this.serialSpecificControlKeys : this.tcpUdpSpecificControlKeys; - const disableKeys = isSerial ? this.tcpUdpSpecificControlKeys : this.serialSpecificControlKeys; - - if (this.isSlaveEnabled) { - enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); - } - - disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); - } - - private updateSlaveConfig(slaveConfig: ModbusSlave): void { - const { - type = ModbusProtocolType.TCP, - method = ModbusMethodType.RTU, - unitId = 0, - deviceName = '', - deviceType = '', - pollPeriod = 5000, - sendDataToThingsBoard = false, - byteOrder = ModbusOrderType.BIG, - wordOrder = ModbusOrderType.BIG, - security = {}, - identity = { - vendorName: '', - productCode: '', - vendorUrl: '', - productName: '', - modelName: '', - }, - values = {} as ModbusRegisterValues, - baudrate = this.modbusBaudrates[0], - host = '', - port = null, - } = slaveConfig; - - const slaveState: ModbusSlave = { - type, - method, - unitId, - deviceName, - deviceType, - pollPeriod, - sendDataToThingsBoard: !!sendDataToThingsBoard, - byteOrder, - wordOrder, - security, - identity, - values, - baudrate, - host: type === ModbusProtocolType.Serial ? '' : host, - port: type === ModbusProtocolType.Serial ? null : port, - serialPort: (type === ModbusProtocolType.Serial ? port : '') as string, - }; - - this.slaveConfigFormGroup.setValue(slaveState, { emitEvent: false }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts deleted file mode 100644 index 137ef385ab..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -/// -/// 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, Inject } from '@angular/core'; -import { - FormBuilder, -} from '@angular/forms'; -import { - LegacySlaveConfig, - ModbusProtocolType, - ModbusSlaveInfo, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; -import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { Router } from '@angular/router'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { - ReportStrategyComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; -import { - ModbusSlaveDialogAbstract -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; - -@Component({ - selector: 'tb-modbus-legacy-slave-dialog', - templateUrl: './modbus-slave-dialog.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusValuesComponent, - ModbusSecurityConfigComponent, - GatewayPortTooltipPipe, - ReportStrategyComponent, - ], - styleUrls: ['./modbus-slave-dialog.component.scss'], -}) -export class ModbusLegacySlaveDialogComponent extends ModbusSlaveDialogAbstract { - - constructor( - protected fb: FormBuilder, - protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, - public dialogRef: MatDialogRef, - ) { - super(fb, store, router, data, dialogRef); - } - - protected override getSlaveResultData(): LegacySlaveConfig { - const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; - const slaveResult = { ...rest, type, ...values }; - - if (type === ModbusProtocolType.Serial) { - slaveResult.port = serialPort; - } - - return slaveResult; - } - - - protected override addFieldsToFormGroup(): void { - this.slaveConfigFormGroup.addControl('sendDataOnlyOnChange', this.fb.control(false)); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts deleted file mode 100644 index f9955b839b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts +++ /dev/null @@ -1,208 +0,0 @@ -/// -/// 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 { Directive, Inject, OnDestroy } from '@angular/core'; -import { - FormBuilder, - FormControl, - UntypedFormGroup, - Validators, -} from '@angular/forms'; -import { - ModbusBaudrates, - ModbusByteSizes, - ModbusMethodLabelsMap, - ModbusMethodType, - ModbusOrderType, - ModbusParity, - ModbusParityLabelsMap, - ModbusProtocolLabelsMap, - ModbusProtocolType, - ModbusSerialMethodType, - ModbusSlaveInfo, - noLeadTrailSpacesRegex, - PortLimits, - ReportStrategyDefaultValue, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Subject } from 'rxjs'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { Router } from '@angular/router'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { takeUntil } from 'rxjs/operators'; -import { isEqual } from '@core/utils'; -import { helpBaseUrl } from '@shared/models/constants'; - -@Directive() -export abstract class ModbusSlaveDialogAbstract extends DialogComponent implements OnDestroy { - - slaveConfigFormGroup: UntypedFormGroup; - showSecurityControl: FormControl; - portLimits = PortLimits; - - readonly modbusProtocolTypes = Object.values(ModbusProtocolType); - readonly modbusMethodTypes = Object.values(ModbusMethodType); - readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); - readonly modbusParities = Object.values(ModbusParity); - readonly modbusByteSizes = ModbusByteSizes; - readonly modbusBaudrates = ModbusBaudrates; - readonly modbusOrderType = Object.values(ModbusOrderType); - readonly ModbusProtocolType = ModbusProtocolType; - readonly ModbusParityLabelsMap = ModbusParityLabelsMap; - readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; - readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; - readonly ReportStrategyDefaultValue = ReportStrategyDefaultValue; - readonly modbusHelpLink = - helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; - - private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; - private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; - - private destroy$ = new Subject(); - - constructor( - protected fb: FormBuilder, - protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, - public dialogRef: MatDialogRef, - ) { - super(store, router, dialogRef); - - this.showSecurityControl = this.fb.control(false); - this.initializeSlaveFormGroup(); - this.updateSlaveFormGroup(); - this.updateControlsEnabling(this.data.value.type); - this.observeTypeChange(); - this.observeShowSecurity(); - this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {})); - } - - get protocolType(): ModbusProtocolType { - return this.slaveConfigFormGroup.get('type').value; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - cancel(): void { - this.dialogRef.close(null); - } - - add(): void { - if (!this.slaveConfigFormGroup.valid) { - return; - } - - this.dialogRef.close(this.getSlaveResultData()); - } - - private initializeSlaveFormGroup(): void { - this.slaveConfigFormGroup = this.fb.group({ - type: [ModbusProtocolType.TCP], - host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], - serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - method: [ModbusMethodType.SOCKET, [Validators.required]], - baudrate: [this.modbusBaudrates[0]], - stopbits: [1], - bytesize: [ModbusByteSizes[0]], - parity: [ModbusParity.None], - strict: [true], - unitId: [null, [Validators.required]], - deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - timeout: [35], - byteOrder: [ModbusOrderType.BIG], - wordOrder: [ModbusOrderType.BIG], - retries: [true], - retryOnEmpty: [true], - retryOnInvalid: [true], - pollPeriod: [5000, [Validators.required]], - connectAttemptTimeMs: [5000, [Validators.required]], - connectAttemptCount: [5, [Validators.required]], - waitAfterFailedAttemptsMs: [300000, [Validators.required]], - values: [{}], - security: [{}], - }); - this.addFieldsToFormGroup(); - } - - private updateSlaveFormGroup(): void { - this.slaveConfigFormGroup.patchValue({ - ...this.data.value, - port: this.data.value.type === ModbusProtocolType.Serial ? null : this.data.value.port, - serialPort: this.data.value.type === ModbusProtocolType.Serial ? this.data.value.port : '', - values: { - attributes: this.data.value.attributes ?? [], - timeseries: this.data.value.timeseries ?? [], - attributeUpdates: this.data.value.attributeUpdates ?? [], - rpc: this.data.value.rpc ?? [], - } - }); - } - - private observeTypeChange(): void { - this.slaveConfigFormGroup.get('type').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(type => { - this.updateControlsEnabling(type); - this.updateMethodType(type); - }); - } - - private updateMethodType(type: ModbusProtocolType): void { - if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { - this.slaveConfigFormGroup.get('method').patchValue( - type === ModbusProtocolType.Serial - ? ModbusSerialMethodType.ASCII - : ModbusMethodType.SOCKET, - {emitEvent: false} - ); - } - } - - private updateControlsEnabling(type: ModbusProtocolType): void { - const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial - ? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys] - : [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys]; - - enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); - disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); - - this.updateSecurityEnabling(this.showSecurityControl.value); - } - - private observeShowSecurity(): void { - this.showSecurityControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => this.updateSecurityEnabling(value)); - } - - private updateSecurityEnabling(isEnabled: boolean): void { - if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) { - this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); - } else { - this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); - } - } - - protected abstract addFieldsToFormGroup(): void; - protected abstract getSlaveResultData(): Config; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html deleted file mode 100644 index 0a480a88ae..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ /dev/null @@ -1,352 +0,0 @@ - -
- -

{{ 'gateway.server-slave' | translate }}

- -
- -
-
-
-
-
-
gateway.server-connection
- - {{ ModbusProtocolLabelsMap.get(type) }} - -
-
-
-
gateway.host
-
- - - - warning - - -
-
-
-
gateway.port
-
- - - - warning - - -
-
- -
-
gateway.port
-
- - - - warning - - -
-
-
-
-
- gateway.method -
-
- - - {{ ModbusMethodLabelsMap.get(method) }} - - -
-
-
- -
-
gateway.baudrate
-
- - - {{ rate }} - - -
-
-
-
gateway.bytesize
-
- - - {{ size }} - - -
-
-
-
gateway.stopbits
-
- - - -
-
-
-
gateway.parity
-
- - - {{ ModbusParityLabelsMap.get(parity) }} - - -
-
-
- - - {{ 'gateway.strict' | translate }} - - -
-
-
-
gateway.unit-id
-
- - - - warning - - -
-
-
-
gateway.device-name
-
- - - - warning - - -
-
-
-
gateway.device-profile
-
- - - - warning - - -
-
-
- - - {{ 'gateway.send-data-on-change' | translate }} - - -
- - - -
- - - -
gateway.advanced-connection-settings
-
-
-
-
-
gateway.connection-timeout
-
- - - -
-
-
-
gateway.byte-order
-
- - - {{ order }} - - -
-
-
-
gateway.word-order
-
- - - {{ order }} - - -
-
-
- - - - - - {{ 'gateway.tls-connection' | translate }} - - - - - - -
-
- - - {{ 'gateway.retries' | translate }} - - -
-
- - - {{ 'gateway.retries-on-empty' | translate }} - - -
-
- - - {{ 'gateway.retries-on-invalid' | translate }} - - -
-
-
- - gateway.poll-period - -
-
- - - -
-
-
-
gateway.connect-attempt-time
-
- - - -
-
-
-
gateway.connect-attempt-count
-
- - - -
-
-
-
gateway.wait-after-failed-attempts
-
- - - -
-
-
-
-
-
- -
-
-
-
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss deleted file mode 100644 index 0c68b6af9e..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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 { - .slaves-config-container { - width: 80vw; - max-width: 900px; - } - - .slave-name-label { - margin-right: 16px; - color: rgba(0, 0, 0, 0.87); - } - - .fixed-title-width-260 { - min-width: 260px; - } - - ::ng-deep.security-config { - .fixed-title-width { - min-width: 230px; - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts deleted file mode 100644 index d9a9c426c2..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -/// -/// 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, Inject } from '@angular/core'; -import { - FormBuilder, -} from '@angular/forms'; -import { - ModbusProtocolType, - ModbusSlaveInfo, - SlaveConfig, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; -import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { Router } from '@angular/router'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { - ReportStrategyComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; -import { - ModbusSlaveDialogAbstract -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; - -@Component({ - selector: 'tb-modbus-slave-dialog', - templateUrl: './modbus-slave-dialog.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusValuesComponent, - ModbusSecurityConfigComponent, - GatewayPortTooltipPipe, - ReportStrategyComponent, - ], - styleUrls: ['./modbus-slave-dialog.component.scss'], -}) -export class ModbusSlaveDialogComponent extends ModbusSlaveDialogAbstract { - - constructor( - protected fb: FormBuilder, - protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, - public dialogRef: MatDialogRef, - ) { - super(fb, store, router, data, dialogRef); - } - - protected override getSlaveResultData(): SlaveConfig { - const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; - const slaveResult = { ...rest, type, ...values }; - - if (type === ModbusProtocolType.Serial) { - slaveResult.port = serialPort; - } - - if (!slaveResult.reportStrategy) { - delete slaveResult.reportStrategy; - } - - return slaveResult; - } - - protected override addFieldsToFormGroup(): void { - this.slaveConfigFormGroup.addControl('reportStrategy', this.fb.control(null)); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html deleted file mode 100644 index 8d1048e3cb..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html +++ /dev/null @@ -1,129 +0,0 @@ - - - -
- -
-
- - - - -
- -
-
-
-
- - -
-
gateway.attributes
-
- - - {{ attribute.tag }} - - - - - - -
-
-
-
gateway.timeseries
-
- - - {{ telemetry.tag }} - - - - - - -
-
-
-
gateway.attribute-updates
-
- - - {{ attributeUpdate.tag }} - - - - - - -
-
-
-
gateway.rpc-requests
-
- - - {{ rpcRequest.tag }} - - - - - - -
-
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.scss deleted file mode 100644 index e80998b88a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 ::ng-deep .mat-mdc-tab-body-wrapper { - min-height: 320px; -} - -::ng-deep .mdc-evolution-chip-set__chips { - align-items: center; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts deleted file mode 100644 index c0824869e3..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts +++ /dev/null @@ -1,240 +0,0 @@ -/// -/// 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, - ChangeDetectorRef, - Component, - forwardRef, - Input, - OnDestroy, - OnInit, - Renderer2, - ViewContainerRef -} from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - ModbusKeysAddKeyTranslationsMap, - ModbusKeysDeleteKeyTranslationsMap, - ModbusKeysNoKeysTextTranslationsMap, - ModbusKeysPanelTitleTranslationsMap, - ModbusRegisterTranslationsMap, - ModbusRegisterType, - ModbusRegisterValues, - ModbusValue, - ModbusValueKey, - ModbusValues, - ModbusValuesState, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; -import { MatButton } from '@angular/material/button'; -import { TbPopoverService } from '@shared/components/popover.service'; -import { ModbusDataKeysPanelComponent } from '../modbus-data-keys-panel/modbus-data-keys-panel.component'; -import { coerceBoolean } from '@shared/decorators/coercion'; - -@Component({ - selector: 'tb-modbus-values', - templateUrl: './modbus-values.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusValuesComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusValuesComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - EllipsisChipListDirective, - ], - styleUrls: ['./modbus-values.component.scss'] -}) - -export class ModbusValuesComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { - - @coerceBoolean() - @Input() singleMode = false; - - @coerceBoolean() - @Input() hideNewFields = false; - - disabled = false; - modbusRegisterTypes: ModbusRegisterType[] = Object.values(ModbusRegisterType); - modbusValueKeys = Object.values(ModbusValueKey); - ModbusValuesTranslationsMap = ModbusRegisterTranslationsMap; - ModbusValueKey = ModbusValueKey; - valuesFormGroup: FormGroup; - - private onChange: (value: ModbusValuesState) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, - private popoverService: TbPopoverService, - private renderer: Renderer2, - private viewContainerRef: ViewContainerRef, - private cdr: ChangeDetectorRef, - ) {} - - ngOnInit() { - this.initializeValuesFormGroup(); - this.observeValuesChanges(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: ModbusValuesState) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(values: ModbusValuesState): void { - if (this.singleMode) { - this.valuesFormGroup.setValue(this.getSingleRegisterState(values as ModbusValues), { emitEvent: false }); - } else { - const { holding_registers, coils_initializer, input_registers, discrete_inputs } = values as ModbusRegisterValues; - this.valuesFormGroup.setValue({ - holding_registers: this.getSingleRegisterState(holding_registers), - coils_initializer: this.getSingleRegisterState(coils_initializer), - input_registers: this.getSingleRegisterState(input_registers), - discrete_inputs: this.getSingleRegisterState(discrete_inputs), - }, { emitEvent: false }); - } - this.cdr.markForCheck(); - } - - validate(): ValidationErrors | null { - return this.valuesFormGroup.valid ? null : { - valuesFormGroup: {valid: false} - }; - } - - setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; - this.cdr.markForCheck(); - } - - getValueGroup(valueKey: ModbusValueKey, register?: ModbusRegisterType): FormGroup { - return register - ? this.valuesFormGroup.get(register).get(valueKey) as FormGroup - : this.valuesFormGroup.get(valueKey) as FormGroup; - } - - manageKeys($event: Event, matButton: MatButton, keysType: ModbusValueKey, register?: ModbusRegisterType): void { - $event.stopPropagation(); - const trigger = matButton._elementRef.nativeElement; - if (this.popoverService.hasPopover(trigger)) { - this.popoverService.hidePopover(trigger); - return; - } - - const keysControl = this.getValueGroup(keysType, register); - const ctx = { - values: keysControl.value, - isMaster: !this.singleMode, - keysType, - panelTitle: ModbusKeysPanelTitleTranslationsMap.get(keysType), - addKeyTitle: ModbusKeysAddKeyTranslationsMap.get(keysType), - deleteKeyTitle: ModbusKeysDeleteKeyTranslationsMap.get(keysType), - noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType), - hideNewFields: this.hideNewFields, - }; - const dataKeysPanelPopover = this.popoverService.displayPopover( - trigger, - this.renderer, - this.viewContainerRef, - ModbusDataKeysPanelComponent, - 'leftBottom', - false, - null, - ctx, - {}, - {}, - {}, - true - ); - dataKeysPanelPopover.tbComponentRef.instance.popover = dataKeysPanelPopover; - dataKeysPanelPopover.tbComponentRef.instance.keysDataApplied.pipe(takeUntil(this.destroy$)).subscribe((keysData: ModbusValue[]) => { - dataKeysPanelPopover.hide(); - keysControl.patchValue(keysData); - keysControl.markAsDirty(); - this.cdr.markForCheck(); - }); - } - - private initializeValuesFormGroup(): void { - const getValuesFormGroup = () => this.fb.group(this.modbusValueKeys.reduce((acc, key) => { - acc[key] = this.fb.control([[], []]); - return acc; - }, {})); - - if (this.singleMode) { - this.valuesFormGroup = getValuesFormGroup(); - } else { - this.valuesFormGroup = this.fb.group( - this.modbusRegisterTypes.reduce((registersAcc, register) => { - registersAcc[register] = getValuesFormGroup(); - return registersAcc; - }, {}) - ); - } - } - - - private observeValuesChanges(): void { - this.valuesFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - private getSingleRegisterState(values: ModbusValues): ModbusValues { - return { - attributes: values?.attributes ?? [], - timeseries: values?.timeseries ?? [], - attributeUpdates: values?.attributeUpdates ?? [], - rpc: values?.rpc ?? [], - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts deleted file mode 100644 index 16f5036770..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts +++ /dev/null @@ -1,93 +0,0 @@ -/// -/// 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 { Directive } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { - BrokerConfig, - MappingType, - MQTTBasicConfig, - MQTTBasicConfig_v3_5_2, - RequestMappingData, - RequestMappingValue, - RequestType, - WorkersConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { isObject } from '@core/utils'; -import { - GatewayConnectorBasicConfigDirective -} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; - -@Directive() -export abstract class MqttBasicConfigDirective - extends GatewayConnectorBasicConfigDirective { - - MappingType = MappingType; - - protected override initBasicFormGroup(): FormGroup { - return this.fb.group({ - mapping: [], - requestsMapping: [], - broker: [], - workers: [], - }); - } - - protected getRequestDataArray(value: Record): RequestMappingData[] { - const mappingConfigs = []; - - if (isObject(value)) { - Object.keys(value).forEach((configKey: string) => { - for (const mapping of value[configKey]) { - mappingConfigs.push({ - requestType: configKey, - requestValue: mapping - }); - } - }); - } - - return mappingConfigs; - } - - protected getRequestDataObject(array: RequestMappingValue[]): Record { - return array.reduce((result, { requestType, requestValue }) => { - result[requestType].push(requestValue); - return result; - }, { - connectRequests: [], - disconnectRequests: [], - attributeRequests: [], - attributeUpdates: [], - serverSideRpc: [], - }); - } - - protected getBrokerMappedValue(broker: BrokerConfig, workers: WorkersConfig): BrokerConfig { - return { - ...broker, - maxNumberOfWorkers: workers.maxNumberOfWorkers ?? 100, - maxMessageNumberPerWorker: workers.maxMessageNumberPerWorker ?? 10, - }; - } - - writeValue(basicConfig: BasicConfig): void { - this.basicFormGroup.setValue(this.mapConfigToFormValue(basicConfig), { emitEvent: false }); - } - - protected abstract override mapConfigToFormValue(config: BasicConfig): MQTTBasicConfig_v3_5_2; - protected abstract override getMappedValue(config: MQTTBasicConfig): BasicConfig; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html deleted file mode 100644 index b602ba20d4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - -
- -
-
- -
- -
-
- -
- -
-
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss deleted file mode 100644 index fd799d8aa4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 { - height: 100%; -} -:host ::ng-deep { - .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { - height: 100%; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts deleted file mode 100644 index 155b91efb6..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts +++ /dev/null @@ -1,97 +0,0 @@ -/// -/// 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 { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; -import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; -import { - BrokerConfig, - MQTTBasicConfig_v3_5_2, - RequestMappingData, - RequestMappingValue, - RequestType, WorkersConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { - MqttBasicConfigDirective -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; - -@Component({ - selector: 'tb-mqtt-basic-config', - templateUrl: './mqtt-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - } - ], - styleUrls: ['./mqtt-basic-config.component.scss'], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, - MappingTableComponent, - ], -}) -export class MqttBasicConfigComponent extends MqttBasicConfigDirective { - - protected override mapConfigToFormValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { - const { broker, mapping = [], requestsMapping } = basicConfig; - return{ - workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { - maxNumberOfWorkers: broker.maxNumberOfWorkers, - maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {} as WorkersConfig, - mapping: mapping ?? [], - broker: broker ?? {} as BrokerConfig, - requestsMapping: this.getRequestDataArray(requestsMapping as Record), - }; - } - - protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { - const { broker, workers, mapping, requestsMapping } = basicConfig || {}; - - return { - broker: this.getBrokerMappedValue(broker, workers), - mapping, - requestsMapping: (requestsMapping as RequestMappingData[])?.length - ? this.getRequestDataObject(requestsMapping as RequestMappingValue[]) - : {} as Record - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts deleted file mode 100644 index 6209cef677..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -/// -/// 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 { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; -import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; -import { - BrokerConfig, - MQTTBasicConfig_v3_5_2, - MQTTLegacyBasicConfig, - RequestMappingData, - RequestMappingValue, - RequestType, WorkersConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; -import { - MqttBasicConfigDirective -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; -import { isDefinedAndNotNull } from '@core/utils'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; - -@Component({ - selector: 'tb-mqtt-legacy-basic-config', - templateUrl: './mqtt-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), - multi: true - } - ], - styleUrls: ['./mqtt-basic-config.component.scss'], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, - MappingTableComponent, - ], -}) -export class MqttLegacyBasicConfigComponent extends MqttBasicConfigDirective { - - protected override mapConfigToFormValue(config: MQTTLegacyBasicConfig): MQTTBasicConfig_v3_5_2 { - const { - broker, - mapping = [], - connectRequests = [], - disconnectRequests = [], - attributeRequests = [], - attributeUpdates = [], - serverSideRpc = [] - } = config as MQTTLegacyBasicConfig; - const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ - connectRequests, - disconnectRequests, - attributeRequests, - attributeUpdates, - serverSideRpc - }); - return { - workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { - maxNumberOfWorkers: broker.maxNumberOfWorkers, - maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {} as WorkersConfig, - mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion(mapping) || [], - broker: broker || {} as BrokerConfig, - requestsMapping: this.getRequestDataArray(updatedRequestMapping), - }; - } - - protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { - const { broker, workers, mapping, requestsMapping } = basicConfig || {}; - - const updatedRequestMapping = (requestsMapping as RequestMappingData[])?.length - ? this.getRequestDataObject(requestsMapping as RequestMappingValue[]) - : {} as Record; - - return { - broker: this.getBrokerMappedValue(broker, workers), - mapping: MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping), - ...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(updatedRequestMapping as Record)) - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html deleted file mode 100644 index b5ef346300..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html +++ /dev/null @@ -1,86 +0,0 @@ - -
-
-
gateway.host
-
- - - - warning - - -
-
-
-
gateway.port
-
- - - - warning - - -
-
-
-
gateway.mqtt-version
-
- - - {{ version.name }} - - -
-
-
-
gateway.client-id
-
- - - - -
-
- - -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts deleted file mode 100644 index 61c62a49e1..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts +++ /dev/null @@ -1,124 +0,0 @@ -/// -/// 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, ChangeDetectorRef, Component, forwardRef, OnDestroy } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { - BrokerConfig, - MqttVersions, - noLeadTrailSpacesRegex, - PortLimits, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { generateSecret } from '@core/utils'; -import { Subject } from 'rxjs'; -import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { SecurityConfigComponent } from '../../security-config/security-config.component'; - -@Component({ - selector: 'tb-broker-config-control', - templateUrl: './broker-config-control.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - GatewayPortTooltipPipe, - ], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => BrokerConfigControlComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => BrokerConfigControlComponent), - multi: true - } - ] -}) -export class BrokerConfigControlComponent implements ControlValueAccessor, Validator, OnDestroy { - brokerConfigFormGroup: UntypedFormGroup; - mqttVersions = MqttVersions; - portLimits = PortLimits; - - private onChange: (value: string) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, - private cdr: ChangeDetectorRef) { - this.brokerConfigFormGroup = this.fb.group({ - host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], - version: [5, []], - clientId: ['tb_gw_' + generateSecret(5), [Validators.pattern(noLeadTrailSpacesRegex)]], - security: [] - }); - - this.brokerConfigFormGroup.valueChanges.subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - generate(formControlName: string): void { - this.brokerConfigFormGroup.get(formControlName)?.patchValue('tb_gw_' + generateSecret(5)); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(brokerConfig: BrokerConfig): void { - const { - version = 5, - clientId = `tb_gw_${generateSecret(5)}`, - security = {}, - } = brokerConfig; - - this.brokerConfigFormGroup.reset({ ...brokerConfig, version, clientId, security }, { emitEvent: false }); - this.cdr.markForCheck(); - } - - validate(): ValidationErrors | null { - return this.brokerConfigFormGroup.valid ? null : { - brokerConfigFormGroup: {valid: false} - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html deleted file mode 100644 index 0a3bd4e6f3..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html +++ /dev/null @@ -1,63 +0,0 @@ - -
-
-
-
{{ 'gateway.max-number-of-workers' | translate }}
-
-
- - - - warning - - -
-
-
-
-
{{ 'gateway.max-messages-queue-for-worker' | translate }}
-
-
- - - - warning - - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts deleted file mode 100644 index 8abc3cb416..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts +++ /dev/null @@ -1,108 +0,0 @@ -/// -/// 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 { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -@Component({ - selector: 'tb-workers-config-control', - templateUrl: './workers-config-control.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => WorkersConfigControlComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => WorkersConfigControlComponent), - multi: true - } - ] -}) -export class WorkersConfigControlComponent implements OnDestroy, ControlValueAccessor, Validator { - - workersConfigFormGroup: UntypedFormGroup; - - private onChange: (value: string) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.workersConfigFormGroup = this.fb.group({ - maxNumberOfWorkers: [100, [Validators.required, Validators.min(1)]], - maxMessageNumberPerWorker: [10, [Validators.required, Validators.min(1)]], - }); - - this.workersConfigFormGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(workersConfig: WorkersConfig): void { - const { maxNumberOfWorkers, maxMessageNumberPerWorker } = workersConfig; - this.workersConfigFormGroup.reset({ - maxNumberOfWorkers: maxNumberOfWorkers || 100, - maxMessageNumberPerWorker: maxMessageNumberPerWorker || 10, - }, {emitEvent: false}); - } - - validate(): ValidationErrors | null { - return this.workersConfigFormGroup.valid ? null : { - workersConfigFormGroup: {valid: false} - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html deleted file mode 100644 index 4a0e76ee19..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html +++ /dev/null @@ -1,148 +0,0 @@ - -
-
-
gateway.server-url
-
- - - - warning - - -
-
-
-
-
{{ 'gateway.timeout' | translate }}
-
-
- - - - warning - - -
-
-
-
-
{{ 'gateway.security-policy' | translate }}
-
-
- - - {{ version.name }} - - -
-
-
-
-
{{ 'gateway.scan-period' | translate }}
-
-
- - - - warning - - -
-
-
-
-
{{ 'gateway.poll-period' | translate }}
-
-
- - - - warning - - -
-
-
-
-
{{ 'gateway.sub-check-period' | translate }}
-
-
- - - - warning - - -
-
-
- - -
{{ 'gateway.enable-subscription' | translate }}
-
-
-
-
- - - {{ 'gateway.show-map' | translate }} - - -
- - -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss deleted file mode 100644 index 416f368279..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts deleted file mode 100644 index c000139ded..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts +++ /dev/null @@ -1,152 +0,0 @@ -/// -/// 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 { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { - noLeadTrailSpacesRegex, - SecurityPolicy, - SecurityPolicyTypes, - ServerConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { HOUR } from '@shared/models/time/time.models'; -import { coerceBoolean } from '@shared/decorators/coercion'; - -@Component({ - selector: 'tb-opc-server-config', - templateUrl: './opc-server-config.component.html', - styleUrls: ['./opc-server-config.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => OpcServerConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => OpcServerConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - ] -}) -export class OpcServerConfigComponent implements ControlValueAccessor, Validator, AfterViewInit, OnDestroy { - - @Input() - @coerceBoolean() - hideNewFields: boolean = false; - - securityPolicyTypes = SecurityPolicyTypes; - serverConfigFormGroup: UntypedFormGroup; - - onChange!: (value: string) => void; - onTouched!: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.serverConfigFormGroup = this.fb.group({ - url: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - timeoutInMillis: [1000, [Validators.required, Validators.min(1000)]], - scanPeriodInMillis: [HOUR, [Validators.required, Validators.min(1000)]], - pollPeriodInMillis: [5000, [Validators.required, Validators.min(50)]], - enableSubscriptions: [true, []], - subCheckPeriodInMillis: [100, [Validators.required, Validators.min(100)]], - showMap: [false, []], - security: [SecurityPolicy.BASIC128, []], - identity: [] - }); - - this.serverConfigFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.onChange(value); - this.onTouched(); - }); - } - - ngAfterViewInit(): void { - if (this.hideNewFields) { - this.serverConfigFormGroup.get('pollPeriodInMillis').disable({emitEvent: false}); - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.serverConfigFormGroup.valid ? null : { - serverConfigFormGroup: { valid: false } - }; - } - - writeValue(serverConfig: ServerConfig): void { - const { - timeoutInMillis = 1000, - scanPeriodInMillis = HOUR, - pollPeriodInMillis = 5000, - enableSubscriptions = true, - subCheckPeriodInMillis = 100, - showMap = false, - security = SecurityPolicy.BASIC128, - identity = {}, - } = serverConfig; - - this.serverConfigFormGroup.reset({ - ...serverConfig, - timeoutInMillis, - scanPeriodInMillis, - pollPeriodInMillis, - enableSubscriptions, - subCheckPeriodInMillis, - showMap, - security, - identity, - }, { emitEvent: false }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html deleted file mode 100644 index 2296a472a4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - -
- -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss deleted file mode 100644 index fd799d8aa4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 { - height: 100%; -} -:host ::ng-deep { - .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { - height: 100%; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts deleted file mode 100644 index c45eafcc67..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -/// -/// 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 } from '@angular/core'; -import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { - MappingType, - OPCBasicConfig_v3_5_2, - ServerConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; -import { - GatewayConnectorBasicConfigDirective -} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; - -@Component({ - selector: 'tb-opc-ua-basic-config', - templateUrl: './opc-ua-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => OpcUaBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => OpcUaBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - MappingTableComponent, - OpcServerConfigComponent, - ], - styleUrls: ['./opc-ua-basic-config.component.scss'] -}) -export class OpcUaBasicConfigComponent extends GatewayConnectorBasicConfigDirective { - - mappingTypes = MappingType; - isLegacy = false; - - protected override initBasicFormGroup(): FormGroup { - return this.fb.group({ - mapping: [], - server: [], - }); - } - - protected override mapConfigToFormValue(config: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { - return { - server: config.server ?? {} as ServerConfig, - mapping: config.mapping ?? [], - }; - } - - protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { - return { - server: value.server, - mapping: value.mapping, - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts deleted file mode 100644 index 9458858cbf..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -/// -/// 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 } from '@angular/core'; -import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { - MappingType, - OPCBasicConfig_v3_5_2, - OPCLegacyBasicConfig, ServerConfig, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; -import { - GatewayConnectorBasicConfigDirective -} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; -import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; - -@Component({ - selector: 'tb-opc-ua-legacy-basic-config', - templateUrl: './opc-ua-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - MappingTableComponent, - OpcServerConfigComponent, - ], - styleUrls: ['./opc-ua-basic-config.component.scss'] -}) -export class OpcUaLegacyBasicConfigComponent extends GatewayConnectorBasicConfigDirective { - - mappingTypes = MappingType; - isLegacy = true; - - protected override initBasicFormGroup(): FormGroup { - return this.fb.group({ - mapping: [], - server: [], - }); - } - - protected override mapConfigToFormValue(config: OPCLegacyBasicConfig): OPCBasicConfig_v3_5_2 { - return { - server: config.server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(config.server) : {} as ServerConfig, - mapping: config.server?.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(config.server.mapping) : [], - }; - } - - protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCLegacyBasicConfig { - return { - server: OpcVersionMappingUtil.mapServerToDowngradedVersion(value), - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html deleted file mode 100644 index dec8292720..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html +++ /dev/null @@ -1,57 +0,0 @@ - -
- - - - - - {{ 'gateway.report-strategy.label' | translate }} - - - - - - - -
gateway.report-strategy.label
- -
- -
-
{{ 'gateway.type' | translate }}
- - - {{ ReportTypeTranslateMap.get(type) | translate }} - - -
-
-
- - gateway.report-strategy.report-period - -
-
- - - -
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts deleted file mode 100644 index 10eaf98f86..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts +++ /dev/null @@ -1,174 +0,0 @@ -/// -/// 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, - Input, - OnDestroy, -} from '@angular/core'; -import { Subject } from 'rxjs'; -import { - ControlValueAccessor, - FormBuilder, - FormControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validators -} from '@angular/forms'; -import { - ReportStrategyConfig, - ReportStrategyDefaultValue, - ReportStrategyType, - ReportStrategyTypeTranslationsMap -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { filter, takeUntil } from 'rxjs/operators'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { - ModbusSecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component'; -import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion'; - -@Component({ - selector: 'tb-report-strategy', - templateUrl: './report-strategy.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ReportStrategyComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ReportStrategyComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ModbusSecurityConfigComponent, - ] -}) -export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy { - - @coerceBoolean() - @Input() isExpansionMode = false; - - @coerceNumber() - @Input() defaultValue = ReportStrategyDefaultValue.Key; - - reportStrategyFormGroup: UntypedFormGroup; - showStrategyControl: FormControl; - - readonly reportStrategyTypes = Object.values(ReportStrategyType); - readonly ReportTypeTranslateMap = ReportStrategyTypeTranslationsMap; - readonly ReportStrategyType = ReportStrategyType; - - private onChange: (value: ReportStrategyConfig) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.showStrategyControl = this.fb.control(false); - - this.reportStrategyFormGroup = this.fb.group({ - type: [{ value: ReportStrategyType.OnReportPeriod, disabled: true }, []], - reportPeriod: [{ value: this.defaultValue, disabled: true }, [Validators.required]], - }); - - this.observeStrategyFormChange(); - this.observeStrategyToggle(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - writeValue(reportStrategyConfig: ReportStrategyConfig): void { - if (this.isExpansionMode) { - this.showStrategyControl.setValue(!!reportStrategyConfig, {emitEvent: false}); - } - if (reportStrategyConfig) { - this.reportStrategyFormGroup.enable({emitEvent: false}); - } - const { type = ReportStrategyType.OnReportPeriod, reportPeriod = this.defaultValue } = reportStrategyConfig ?? {}; - this.reportStrategyFormGroup.setValue({ type, reportPeriod }, {emitEvent: false}); - this.onTypeChange(type); - } - - validate(): ValidationErrors | null { - return this.reportStrategyFormGroup.valid || this.reportStrategyFormGroup.disabled ? null : { - reportStrategyForm: { valid: false } - }; - } - - registerOnChange(fn: (value: ReportStrategyConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - private observeStrategyFormChange(): void { - this.reportStrategyFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.onChange(value); - this.onTouched(); - }); - - this.reportStrategyFormGroup.get('type').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(type => this.onTypeChange(type)); - } - - private observeStrategyToggle(): void { - this.showStrategyControl.valueChanges - .pipe(takeUntil(this.destroy$), filter(() => this.isExpansionMode)) - .subscribe(enable => { - if (enable) { - this.reportStrategyFormGroup.enable({emitEvent: false}); - this.reportStrategyFormGroup.get('reportPeriod').addValidators(Validators.required); - this.onChange(this.reportStrategyFormGroup.value); - } else { - this.reportStrategyFormGroup.disable({emitEvent: false}); - this.reportStrategyFormGroup.get('reportPeriod').removeValidators(Validators.required); - this.onChange(null); - } - this.reportStrategyFormGroup.updateValueAndValidity({emitEvent: false}); - }); - } - - private onTypeChange(type: ReportStrategyType): void { - const reportPeriodControl = this.reportStrategyFormGroup.get('reportPeriod'); - - if (type === ReportStrategyType.OnChange) { - reportPeriodControl.disable({emitEvent: false}); - } else if (!this.isExpansionMode || this.showStrategyControl.value) { - reportPeriodControl.enable({emitEvent: false}); - } - } -} 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 deleted file mode 100644 index afabe53499..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.html +++ /dev/null @@ -1,65 +0,0 @@ - -
-
-
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 deleted file mode 100644 index f014f56588..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.scss +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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 deleted file mode 100644 index d00813bfd6..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -/// -/// 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/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html deleted file mode 100644 index 7122bcd52d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html +++ /dev/null @@ -1,81 +0,0 @@ - - -
- {{ 'gateway.rpc.hint.modbus-response-reading' | translate }}
- {{ 'gateway.rpc.hint.modbus-writing-functions' | translate }} -
-
- - {{ 'gateway.rpc.type' | translate }} - - {{ type }} - - - - {{ 'gateway.rpc.functionCode' | translate }} - - {{ ModbusFunctionCodeTranslationsMap.get(code) | translate}} - - -
-
- - {{ 'gateway.rpc.address' | translate }} - - - warning - - - - {{ 'gateway.rpc.objectsCount' | translate }} - - -
-
- - {{ 'gateway.rpc.value' | translate }} - - - warning - - -
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss deleted file mode 100644 index 62eaca664f..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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 { - .hint-container { - margin-bottom: 12px; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts deleted file mode 100644 index ae110b9418..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts +++ /dev/null @@ -1,166 +0,0 @@ -/// -/// 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 { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { - ModbusDataType, - ModbusEditableDataTypes, - ModbusFunctionCodeTranslationsMap, - ModbusObjectCountByDataType, - noLeadTrailSpacesRegex, - RPCTemplateConfigModbus, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Component({ - selector: 'tb-modbus-rpc-parameters', - templateUrl: './modbus-rpc-parameters.component.html', - styleUrls: ['./modbus-rpc-parameters.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusRpcParametersComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusRpcParametersComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], -}) -export class ModbusRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { - - rpcParametersFormGroup: UntypedFormGroup; - functionCodes: Array; - - readonly ModbusEditableDataTypes = ModbusEditableDataTypes; - readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap; - - readonly modbusDataTypes = Object.values(ModbusDataType) as ModbusDataType[]; - readonly writeFunctionCodes = [5, 6, 15, 16]; - - private readonly defaultFunctionCodes = [3, 4, 6, 16]; - private readonly readFunctionCodes = [1, 2, 3, 4]; - private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes]; - - private onChange: (value: RPCTemplateConfigModbus) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.rpcParametersFormGroup = this.fb.group({ - type: [ModbusDataType.BYTES, [Validators.required]], - functionCode: [this.defaultFunctionCodes[0], [Validators.required]], - value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - address: [null, [Validators.required]], - objectsCount: [1, [Validators.required]], - }); - - this.updateFunctionCodes(this.rpcParametersFormGroup.get('type').value); - this.observeValueChanges(); - this.observeKeyDataType(); - this.observeFunctionCode(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: RPCTemplateConfigModbus) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.rpcParametersFormGroup.valid ? null : { - rpcParametersFormGroup: { valid: false } - }; - } - - writeValue(value: RPCTemplateConfigModbus): void { - this.rpcParametersFormGroup.patchValue(value, {emitEvent: false}); - } - - private observeValueChanges(): void { - this.rpcParametersFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.onChange(value); - this.onTouched(); - }); - } - - private observeKeyDataType(): void { - this.rpcParametersFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => { - if (!this.ModbusEditableDataTypes.includes(dataType)) { - this.rpcParametersFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false}); - } - this.updateFunctionCodes(dataType); - }); - } - - private observeFunctionCode(): void { - this.rpcParametersFormGroup.get('functionCode').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(code => this.updateValueEnabling(code)); - } - - private updateValueEnabling(code: number): void { - if (this.writeFunctionCodes.includes(code)) { - this.rpcParametersFormGroup.get('value').enable({emitEvent: false}); - } else { - this.rpcParametersFormGroup.get('value').setValue(null); - this.rpcParametersFormGroup.get('value').disable({emitEvent: false}); - } - } - - private updateFunctionCodes(dataType: ModbusDataType): void { - this.functionCodes = dataType === ModbusDataType.BITS ? this.bitsFunctionCodes : this.defaultFunctionCodes; - if (!this.functionCodes.includes(this.rpcParametersFormGroup.get('functionCode').value)) { - this.rpcParametersFormGroup.get('functionCode').patchValue(this.functionCodes[0], {emitEvent: false}); - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html deleted file mode 100644 index eb66a6df8c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - {{ 'gateway.rpc.method-name' | translate }} - - - - {{ 'gateway.rpc.requestTopicExpression' | translate }} - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - {{ 'gateway.rpc.responseTopicExpression' | translate }} - - - - {{ 'gateway.rpc.responseTimeout' | translate }} - - - - {{ 'gateway.rpc.valueExpression' | translate }} - - - - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss deleted file mode 100644 index a2dddebc47..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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 { - display: flex; - flex-direction: column; - - .mat-mdc-slide-toggle.margin { - margin-bottom: 10px; - margin-left: 10px; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts deleted file mode 100644 index 56d9510e7d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -/// -/// 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 { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, Validators, -} from '@angular/forms'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil, tap } from 'rxjs/operators'; -import { - integerRegex, - noLeadTrailSpacesRegex, - RPCTemplateConfigMQTT -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Component({ - selector: 'tb-mqtt-rpc-parameters', - templateUrl: './mqtt-rpc-parameters.component.html', - styleUrls: ['./mqtt-rpc-parameters.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttRpcParametersComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttRpcParametersComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], -}) -export class MqttRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { - - rpcParametersFormGroup: UntypedFormGroup; - - private onChange: (value: RPCTemplateConfigMQTT) => void = (_) => {}; - private onTouched: () => void = () => {}; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.rpcParametersFormGroup = this.fb.group({ - methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTimeout: [{ value: null, disabled: true }, [Validators.min(10), Validators.pattern(integerRegex)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - withResponse: [false, []], - }); - - this.observeValueChanges(); - this.observeWithResponse(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: RPCTemplateConfigMQTT) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.rpcParametersFormGroup.valid ? null : { - rpcParametersFormGroup: { valid: false } - }; - } - - writeValue(value: RPCTemplateConfigMQTT): void { - this.rpcParametersFormGroup.patchValue(value, {emitEvent: false}); - this.toggleResponseFields(value.withResponse); - } - - private observeValueChanges(): void { - this.rpcParametersFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.onChange(value); - this.onTouched(); - }); - } - - private observeWithResponse(): void { - this.rpcParametersFormGroup.get('withResponse').valueChanges.pipe( - tap((isActive: boolean) => this.toggleResponseFields(isActive)), - takeUntil(this.destroy$), - ).subscribe(); - } - - private toggleResponseFields(enabled: boolean): void { - const responseTopicControl = this.rpcParametersFormGroup.get('responseTopicExpression'); - const responseTimeoutControl = this.rpcParametersFormGroup.get('responseTimeout'); - if (enabled) { - responseTopicControl.enable(); - responseTimeoutControl.enable(); - } else { - responseTopicControl.disable(); - responseTimeoutControl.disable(); - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html deleted file mode 100644 index ec5c20cfb1..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html +++ /dev/null @@ -1,93 +0,0 @@ - - -
- {{ 'gateway.rpc.hint.opc-method' | translate }} -
- - {{ 'gateway.rpc.method' | translate }} - - -
- - {{ 'gateway.rpc.arguments' | translate }} - -
-
-
gateway.type
-
- - - -
- - - {{ valueTypes.get(argumentFormGroup.get('type').value)?.name | translate }} -
-
- - - - {{ valueTypes.get(valueType).name | translate }} - -
-
-
-
-
-
gateway.value
- - - - - - - true - false - - - - warning - - -
- -
- -
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss deleted file mode 100644 index 5108cc70b1..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 { - .arguments-container { - margin-bottom: 10px; - } - - .type-container { - width: 40%; - } - - .value-container { - width: 50%; - } - - .hint-container { - margin-bottom: 12px; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts deleted file mode 100644 index 0c0dbea3bd..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts +++ /dev/null @@ -1,169 +0,0 @@ -/// -/// 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, - ChangeDetectorRef, - Component, - forwardRef, - OnDestroy, -} from '@angular/core'; -import { - ControlValueAccessor, - FormArray, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validator, Validators, -} from '@angular/forms'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { - integerRegex, - MappingValueType, - mappingValueTypesMap, - noLeadTrailSpacesRegex, - OPCTypeValue, - RPCTemplateConfigOPC -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { isDefinedAndNotNull, isEqual } from '@core/utils'; - -@Component({ - selector: 'tb-opc-rpc-parameters', - templateUrl: './opc-rpc-parameters.component.html', - styleUrls: ['./opc-rpc-parameters.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => OpcRpcParametersComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => OpcRpcParametersComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - ], -}) -export class OpcRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { - - rpcParametersFormGroup: UntypedFormGroup; - - readonly valueTypeKeys: MappingValueType[] = Object.values(MappingValueType); - readonly MappingValueType = MappingValueType; - readonly valueTypes = mappingValueTypesMap; - - private onChange: (value: RPCTemplateConfigOPC) => void = (_) => {} ; - private onTouched: () => void = () => {}; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) { - this.rpcParametersFormGroup = this.fb.group({ - method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - arguments: this.fb.array([]), - }); - - this.observeValueChanges(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: RPCTemplateConfigOPC) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - validate(): ValidationErrors | null { - return this.rpcParametersFormGroup.valid ? null : { - rpcParametersFormGroup: { valid: false } - }; - } - - writeValue(params: RPCTemplateConfigOPC): void { - this.clearArguments(); - params.arguments?.map(({type, value}) => ({type, [type]: value })) - .forEach(argument => this.addArgument(argument as OPCTypeValue)); - this.cdr.markForCheck(); - this.rpcParametersFormGroup.get('method').patchValue(params.method); - } - - private observeValueChanges(): void { - this.rpcParametersFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe(params => { - const updatedArguments = params.arguments.map(({type, ...config}) => ({type, value: config[type]})); - this.onChange({method: params.method, arguments: updatedArguments}); - this.onTouched(); - }); - } - - removeArgument(index: number): void { - (this.rpcParametersFormGroup.get('arguments') as FormArray).removeAt(index); - } - - addArgument(value: OPCTypeValue = {} as OPCTypeValue): void { - const fromGroup = this.fb.group({ - type: [value.type ?? MappingValueType.STRING], - string: [ - value.string ?? { value: '', disabled: !(isEqual(value, {}) || value.string)}, - [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)] - ], - integer: [ - {value: value.integer ?? 0, disabled: !isDefinedAndNotNull(value.integer)}, - [Validators.required, Validators.pattern(integerRegex)] - ], - double: [{value: value.double ?? 0, disabled: !isDefinedAndNotNull(value.double)}, [Validators.required]], - boolean: [{value: value.boolean ?? false, disabled: !isDefinedAndNotNull(value.boolean)}, [Validators.required]], - }); - this.observeTypeChange(fromGroup); - (this.rpcParametersFormGroup.get('arguments') as FormArray).push(fromGroup, {emitEvent: false}); - } - - clearArguments(): void { - const formArray = this.rpcParametersFormGroup.get('arguments') as FormArray; - while (formArray.length !== 0) { - formArray.removeAt(0); - } - } - - private observeTypeChange(dataKeyFormGroup: FormGroup): void { - dataKeyFormGroup.get('type').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(type => { - dataKeyFormGroup.disable({emitEvent: false}); - dataKeyFormGroup.get('type').enable({emitEvent: false}); - dataKeyFormGroup.get(type).enable({emitEvent: false}); - }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.html deleted file mode 100644 index 87976abf08..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.html +++ /dev/null @@ -1,128 +0,0 @@ - -
-
-
{{ title | translate }}
- - - {{ SecurityTypeTranslationsMap.get(type) | translate }} - - -
- - -
-
gateway.username
-
- - - - warning - - -
-
-
-
gateway.password
-
- - -
- -
-
-
-
-
- -
{{ 'gateway.path-hint' | translate }}
-
-
gateway.CA-certificate-path
-
- - - -
-
-
-
gateway.private-key-path
-
- - - -
-
-
-
gateway.client-cert-path
-
- - - -
-
- -
-
gateway.mode
-
- - - - {{ type }} - - - -
-
-
-
gateway.username
-
- - - - warning - - -
-
-
-
gateway.password
-
- - -
- -
-
-
-
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.scss deleted file mode 100644 index 416f368279..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts deleted file mode 100644 index a6ee3ce93d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts +++ /dev/null @@ -1,177 +0,0 @@ -/// -/// 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, - ChangeDetectorRef, - Component, - forwardRef, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; -import { Subject } from 'rxjs'; -import { - ControlValueAccessor, - FormBuilder, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - ValidationErrors, - Validators -} from '@angular/forms'; -import { - SecurityType, - SecurityTypeTranslationsMap, - ModeType, - noLeadTrailSpacesRegex, - ConnectorSecurity -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { takeUntil } from 'rxjs/operators'; -import { coerceBoolean } from '@shared/decorators/coercion'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'tb-security-config', - templateUrl: './security-config.component.html', - styleUrls: ['./security-config.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => SecurityConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => SecurityConfigComponent), - multi: true - } - ], - standalone: true, - imports:[ - CommonModule, - SharedModule, - ] -}) -export class SecurityConfigComponent implements ControlValueAccessor, OnInit, OnDestroy { - - @Input() - title = 'gateway.security'; - - @Input() - @coerceBoolean() - extendCertificatesModel = false; - - BrokerSecurityType = SecurityType; - securityTypes = Object.values(SecurityType) as SecurityType[]; - modeTypes = Object.values(ModeType); - SecurityTypeTranslationsMap = SecurityTypeTranslationsMap; - securityFormGroup: UntypedFormGroup; - - private onChange: (value: string) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {} - - ngOnInit(): void { - this.securityFormGroup = this.fb.group({ - type: [SecurityType.ANONYMOUS, []], - username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - password: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - pathToCACert: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - pathToPrivateKey: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - pathToClientCert: ['', [Validators.pattern(noLeadTrailSpacesRegex)]] - }); - if (this.extendCertificatesModel) { - this.securityFormGroup.addControl('mode', this.fb.control(ModeType.NONE, [])); - } - this.securityFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.onChange(value); - this.onTouched(); - }); - this.securityFormGroup.get('type').valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((type) => this.updateValidators(type)); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - writeValue(securityInfo: ConnectorSecurity): void { - if (!securityInfo) { - const defaultSecurity = {type: SecurityType.ANONYMOUS}; - this.securityFormGroup.reset(defaultSecurity, {emitEvent: false}); - } else { - if (!securityInfo.type) { - securityInfo.type = SecurityType.ANONYMOUS; - } - this.updateValidators(securityInfo.type); - this.securityFormGroup.reset(securityInfo, {emitEvent: false}); - } - this.cdr.markForCheck(); - } - - validate(): ValidationErrors | null { - return this.securityFormGroup.get('type').value !== SecurityType.BASIC || this.securityFormGroup.valid ? null : { - securityForm: { valid: false } - }; - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - private updateValidators(type: SecurityType): void { - if (type) { - this.securityFormGroup.get('username').disable({emitEvent: false}); - this.securityFormGroup.get('password').disable({emitEvent: false}); - this.securityFormGroup.get('pathToCACert').disable({emitEvent: false}); - this.securityFormGroup.get('pathToPrivateKey').disable({emitEvent: false}); - this.securityFormGroup.get('pathToClientCert').disable({emitEvent: false}); - this.securityFormGroup.get('mode')?.disable({emitEvent: false}); - if (type === SecurityType.BASIC) { - this.securityFormGroup.get('username').enable({emitEvent: false}); - this.securityFormGroup.get('password').enable({emitEvent: false}); - } else if (type === SecurityType.CERTIFICATES) { - this.securityFormGroup.get('pathToCACert').enable({emitEvent: false}); - this.securityFormGroup.get('pathToPrivateKey').enable({emitEvent: false}); - this.securityFormGroup.get('pathToClientCert').enable({emitEvent: false}); - if (this.extendCertificatesModel) { - const modeControl = this.securityFormGroup.get('mode'); - if (modeControl && !modeControl.value) { - modeControl.setValue(ModeType.NONE, {emitEvent: false}); - } - - modeControl?.enable({emitEvent: false}); - this.securityFormGroup.get('username').enable({emitEvent: false}); - this.securityFormGroup.get('password').enable({emitEvent: false}); - } - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html deleted file mode 100644 index f72a2c18af..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html +++ /dev/null @@ -1,103 +0,0 @@ - -
-
-
-
- - - - -
{{ valueTitle(keyControl.get(keyControl.get('type').value).value) }}
-
-
- -
-
gateway.type
-
- - - -
- - - - {{ valueTypes.get(keyControl.get('type').value)?.name | translate}} - -
-
- - - - {{ valueTypes.get(valueType).name | translate }} - -
-
-
-
-
-
gateway.value
- - - - - - - true - false - - - - warning - - -
-
-
-
-
- -
-
-
- -
-
- -
- {{ 'gateway.no-value' }} -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss deleted file mode 100644 index 770f17cac6..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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 { - - .title-container { - max-width: 11vw; - } - - .key-panel { - height: 250px; - overflow: auto; - } - - .tb-form-panel { - .mat-mdc-icon-button { - width: 56px; - height: 56px; - padding: 16px; - color: rgba(0, 0, 0, 0.54); - } - } - - .see-example { - width: 32px; - height: 32px; - margin: 4px; - } -} - -:host ::ng-deep { - .mat-mdc-form-field-icon-suffix { - display: flex; - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts deleted file mode 100644 index 27096ea25a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts +++ /dev/null @@ -1,160 +0,0 @@ -/// -/// 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 { Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; -import { - AbstractControl, - ControlValueAccessor, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormArray, - UntypedFormBuilder, - ValidationErrors, - Validator, - Validators -} from '@angular/forms'; -import { isDefinedAndNotNull } from '@core/utils'; -import { - integerRegex, - MappingDataKey, - MappingValueType, - mappingValueTypesMap, - noLeadTrailSpacesRegex -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; - -@Component({ - selector: 'tb-type-value-panel', - templateUrl: './type-value-panel.component.html', - styleUrls: ['./type-value-panel.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => TypeValuePanelComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => TypeValuePanelComponent), - multi: true - } - ] -}) -export class TypeValuePanelComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { - - valueTypeKeys: MappingValueType[] = Object.values(MappingValueType); - valueTypes = mappingValueTypesMap; - valueListFormArray: UntypedFormArray; - readonly MappingValueType = MappingValueType; - - private destroy$ = new Subject(); - private propagateChange = (v: any) => {}; - - constructor(private fb: UntypedFormBuilder) {} - - ngOnInit(): void { - this.valueListFormArray = this.fb.array([]); - this.valueListFormArray.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - this.updateView(value); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - trackByKey(_: number, keyControl: AbstractControl): any { - return keyControl; - } - - addKey(): void { - const dataKeyFormGroup = this.fb.group({ - type: [MappingValueType.STRING], - string: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - integer: [{value: 0, disabled: true}, [Validators.required, Validators.pattern(integerRegex)]], - double: [{value: 0, disabled: true}, [Validators.required]], - boolean: [{value: false, disabled: true}, [Validators.required]], - }); - this.observeTypeChange(dataKeyFormGroup); - this.valueListFormArray.push(dataKeyFormGroup); - } - - private observeTypeChange(dataKeyFormGroup: FormGroup): void { - dataKeyFormGroup.get('type').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(type => { - dataKeyFormGroup.disable({emitEvent: false}); - dataKeyFormGroup.get('type').enable({emitEvent: false}); - dataKeyFormGroup.get(type).enable({emitEvent: false}); - }); - } - - deleteKey($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - } - this.valueListFormArray.removeAt(index); - this.valueListFormArray.markAsDirty(); - } - - valueTitle(value: any): string { - if (isDefinedAndNotNull(value)) { - if (typeof value === 'object') { - return JSON.stringify(value); - } - return value; - } - return ''; - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void {} - - writeValue(deviceInfoArray: Array): void { - for (const deviceInfo of deviceInfoArray) { - const config = { - type: [deviceInfo.type], - string: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - integer: [{value: 0, disabled: true}, [Validators.required, Validators.pattern(integerRegex)]], - double: [{value: 0, disabled: true}, [Validators.required]], - boolean: [{value: false, disabled: true}, [Validators.required]], - }; - config[deviceInfo.type][0] = {value: deviceInfo.value, disabled: false}; - - const dataKeyFormGroup = this.fb.group(config); - this.observeTypeChange(dataKeyFormGroup); - this.valueListFormArray.push(dataKeyFormGroup); - } - } - - validate(): ValidationErrors | null { - return this.valueListFormArray.valid ? null : { - valueListForm: { valid: false } - }; - } - - private updateView(value: any): void { - this.propagateChange(value.map(({type, ...config}) => ({type, value: config[type]}))); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.html deleted file mode 100644 index 87665b28fa..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.html +++ /dev/null @@ -1,53 +0,0 @@ - -
-
{{ 'gateway.docker-label' | translate }}
-
-
device.connectivity.install-necessary-client-tools
- -
- -
-
gateway.download-configuration-file
-
-
gateway.download-docker-compose
- -
-
- -
-
gateway.launch-gateway
-
gateway.launch-docker-compose
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss deleted file mode 100644 index 3f66b67943..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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 { - .tb-commands-hint { - color: inherit; - font-weight: normal; - flex: 1; - } -} - -:host ::ng-deep { - .tb-markdown-view { - .start-code { - .code-wrapper { - padding: 0; - - pre[class*=language-] { - margin: 0; - background: #F3F6FA; - border-color: #305680; - padding-right: 38px; - overflow: scroll; - padding-bottom: 4px; - min-height: 42px; - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: 4px; - height: 4px; - } - } - } - button.clipboard-btn { - right: -2px; - p { - color: #305680; - } - p, div { - background-color: #F3F6FA; - } - div { - img { - display: none; - } - &:after { - content: ""; - position: initial; - display: block; - width: 18px; - height: 18px; - background: #305680; - mask-image: url(/assets/copy-code-icon.svg); - -webkit-mask-image: url(/assets/copy-code-icon.svg); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - } - } - } - } - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts deleted file mode 100644 index 8465b5879d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -/// -/// 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 { Component, Input } from '@angular/core'; -import { DeviceService } from '@core/http/device.service'; - -@Component({ - selector: 'tb-gateway-command', - templateUrl: './device-gateway-command.component.html', - styleUrls: ['./device-gateway-command.component.scss'] -}) - -export class DeviceGatewayCommandComponent { - - @Input() - deviceId: string; - - constructor(private deviceService: DeviceService) { - } - - download($event: Event) { - if ($event) { - $event.stopPropagation(); - } - if (this.deviceId) { - this.deviceService.downloadGatewayDockerComposeFile(this.deviceId).subscribe(() => {}); - } - } -} 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 deleted file mode 100644 index d995136b27..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.html +++ /dev/null @@ -1,117 +0,0 @@ - -
- -

{{ "gateway.add-connector" | translate}}

- -
- -
-
-
-
-
gateway.type
-
- - - - {{ type.value }} - - - -
-
-
-
gateway.name
-
- - - - warning - - -
-
-
-
gateway.connectors-table-class
-
- - - -
-
-
-
gateway.connectors-table-key
-
- - - -
-
-
-
gateway.remote-logging-level
-
- - - {{ logLevel }} - - -
-
-
- - - {{ 'gateway.fill-connector-defaults' | translate }} - - -
-
- - - {{ 'gateway.send-change-data' | translate }} - - -
-
-
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss deleted file mode 100644 index 56e4bc229c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.scss +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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 { - .add-connector { - min-width: 400px; - width: 500px; - } -} 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 deleted file mode 100644 index 1c0f74e8e5..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -/// -/// 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 { 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'; -import { FormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms'; -import { BaseData, HasId } from '@shared/models/base-data'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Router } from '@angular/router'; -import { - AddConnectorConfigData, - ConnectorType, - CreatedConnectorConfigData, - GatewayConnector, - GatewayConnectorDefaultTypesTranslatesMap, - GatewayLogLevel, - GatewayVersion, - GatewayVersionedDefaultConfig, - noLeadTrailSpacesRegex -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Observable, Subject } from 'rxjs'; -import { ResourcesService } from '@core/services/resources.service'; -import { takeUntil, tap } from 'rxjs/operators'; -import { helpBaseUrl } from '@shared/models/constants'; -import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe'; - -@Component({ - selector: 'tb-add-connector-dialog', - templateUrl: './add-connector-dialog.component.html', - styleUrls: ['./add-connector-dialog.component.scss'], - providers: [], -}) -export class AddConnectorDialogComponent - extends DialogComponent> implements OnInit, OnDestroy { - - connectorForm: UntypedFormGroup; - - connectorType = ConnectorType; - - gatewayConnectorDefaultTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap; - gatewayLogLevel = Object.values(GatewayLogLevel); - - submitted = false; - - private destroy$ = new Subject(); - - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: AddConnectorConfigData, - public dialogRef: MatDialogRef, - private fb: FormBuilder, - private isLatestVersionConfig: LatestVersionConfigPipe, - private resourcesService: ResourcesService) { - super(store, router, dialogRef); - this.connectorForm = this.fb.group({ - type: [ConnectorType.MQTT, []], - name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]], - logLevel: [GatewayLogLevel.INFO, []], - useDefaults: [true, []], - sendDataOnlyOnChange: [false, []], - class: ['', []], - key: ['auto', []], - }); - } - - ngOnInit(): void { - this.observeTypeChange(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - } - - helpLinkId(): string { - return helpBaseUrl + '/docs/iot-gateway/configuration/'; - } - - cancel(): void { - this.dialogRef.close(null); - } - - add(): void { - this.submitted = true; - const value = this.connectorForm.getRawValue(); - if (value.useDefaults) { - this.getDefaultConfig(value.type).subscribe((defaultConfig: GatewayVersionedDefaultConfig) => { - const gatewayVersion = this.data.gatewayVersion; - if (gatewayVersion) { - value.configVersion = gatewayVersion; - } - value.configurationJson = (this.isLatestVersionConfig.transform(gatewayVersion) - ? defaultConfig[GatewayVersion.Current] - : defaultConfig[GatewayVersion.Legacy]) - ?? defaultConfig; - if (this.connectorForm.valid) { - this.dialogRef.close(value); - } - }); - } else if (this.connectorForm.valid) { - this.dialogRef.close(value); - } - } - - private uniqNameRequired(): ValidatorFn { - return (control: UntypedFormControl) => { - const newName = control.value.trim().toLowerCase(); - const isDuplicate = this.data.dataSourceData.some(({ value: { name } }) => - name.toLowerCase() === newName - ); - - return isDuplicate ? { duplicateName: { valid: false } } : 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(); - } - - private getDefaultConfig(type: string): Observable { - return this.resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); - }; -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html deleted file mode 100644 index b56449892b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.html +++ /dev/null @@ -1,766 +0,0 @@ - -
- -

{{ MappingTypeTranslationsMap.get(this.data?.mappingType) | translate}}

- -
- -
-
-
-
- {{ MappingHintTranslationsMap.get(this.data?.mappingType) | translate }} -
- - -
-
gateway.topic-filter
-
- - - - warning - -
-
-
-
-
-
-
- {{ 'gateway.mqtt-qos' | translate }} -
-
- - - - {{ QualityTranslationsMap.get(type) | translate }} - - - -
-
- -
-
gateway.payload-type
- - - {{ ConvertorTypeTranslationsMap.get(type) | translate }} - - -
-
-
gateway.data-conversion
-
- {{ DataConversionTranslationsMap.get(converterType) | translate }} -
- - - - - - - - - -
-
-
gateway.attributes
-
- - - {{ attribute }} - - - - - - -
-
-
-
gateway.timeseries
-
- - - {{ telemetry }} - - - - - - -
-
-
-
-
-
- {{ 'gateway.extension' | translate }} -
-
- - - - warning - - -
-
-
-
gateway.extension-configuration
-
{{ 'gateway.extension-configuration-hint' | translate }}
-
-
gateway.keys
-
- - - {{ telemetry }} - - - - - - -
-
-
-
-
-
-
-
- -
-
gateway.request-type
-
- - - - {{ RequestTypesTranslationsMap.get(type) | translate }} - - - -
-
- - -
-
gateway.topic-filter
-
- - - - warning - -
-
-
-
-
- - - - - - - - - -
-
gateway.from-device-request-settings
-
- gateway.from-device-request-settings-hint -
-
-
-
gateway.device-info.device-name-expression
-
-
- - - - {{ SourceTypeTranslationsMap.get(type) | translate }} - - - - - - - warning - -
-
-
-
-
-
-
gateway.attribute-name-expression
-
- - - - {{ SourceTypeTranslationsMap.get(type) | translate }} - - - - - - - warning - -
-
-
-
-
-
-
-
gateway.to-device-response-settings
-
- gateway.to-device-response-settings-hint -
-
-
gateway.response-value-expression
-
- - - - warning - -
-
-
-
-
-
-
gateway.response-topic-expression
-
- - - - warning - -
-
-
-
-
-
- - - {{ 'gateway.retain' | translate }} - - -
-
-
- -
-
- {{ 'gateway.device-name-filter' | translate }} -
-
- - - - warning - - -
-
-
-
- {{ 'gateway.attribute-filter' | translate }} -
-
- - - - warning - - -
-
-
-
gateway.response-value-expression
-
- - - - warning - -
-
-
-
-
-
-
gateway.response-topic-expression
-
- - - - warning - -
-
-
-
-
-
- - - {{ 'gateway.retain' | translate }} - - -
-
- -
- - - {{ 'gateway.with-response' | translate }} - - - {{ 'gateway.without-response' | translate }} - - -
-
-
- {{ 'gateway.device-name-filter' | translate }} -
-
- - - - warning - - -
-
-
-
- {{ 'gateway.method-filter' | translate }} -
-
- - - - warning - - -
-
-
-
gateway.request-topic-expression
-
- - - - warning - -
-
-
-
-
-
-
gateway.value-expression
-
- - - - warning - -
-
-
-
-
- -
-
gateway.response-topic-expression
-
- - - - warning - -
-
-
-
-
-
-
- {{ 'gateway.response-topic-Qos' | translate }} -
- - - - {{ QualityTranslationsMap.get(type) | translate }} - - - -
-
-
gateway.response-timeout
-
- - - - warning - - -
-
-
-
-
-
-
- -
-
-
- {{ 'gateway.device-node' | translate }} -
-
-
- - - - {{ SourceTypeTranslationsMap.get(type) | translate }} - - - - - - - warning - -
-
-
-
-
- - -
-
gateway.attributes
-
- - - {{ attribute }} - - - - - - -
-
-
-
gateway.timeseries
-
- - - {{ telemetry }} - - - - - - -
-
-
-
gateway.attribute-updates
-
- - - {{ attribute }} - - - - - - -
-
-
-
gateway.rpc-methods
-
- - - {{ attribute }} - - - - - - -
-
-
-
-
-
-
- - -
-
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 deleted file mode 100644 index 4212e8f834..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.scss +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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 { - display: grid; - height: 100%; - - .key-mapping { - max-width: 900px; - display: flex; - flex-direction: column; - - .mat-toolbar { - min-height: 64px; - } - - tb-toggle-select { - padding: 4px 0; - } - } - - .mat-mdc-dialog-content { - height: 670px; - } - - .ellipsis-chips-container { - max-width: 70%; - } -} - -:host ::ng-deep { - .key-mapping { - .mat-mdc-chip-listbox { - .mdc-evolution-chip-set__chips { - justify-content: flex-end; - align-items: center; - flex-wrap: nowrap; - } - } - } - .tb-form-row { - .fixed-title-width { - min-width: 40px; - width: 35%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .mat-mdc-form-field { - width: 0; - } - } - - .see-example { - width: 32px; - height: 32px; - margin: 4px; - } - - .mat-mdc-form-field-icon-suffix { - display: flex; - } - - .device-config { - gap: 12px; - padding-left: 10px; - padding-right: 10px; - } - - .device-node-pattern-field { - flex-basis: 3%; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts deleted file mode 100644 index ef8d7ed7ce..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts +++ /dev/null @@ -1,421 +0,0 @@ -/// -/// 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 { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Router } from '@angular/router'; -import { - Attribute, - AttributesUpdate, - ConnectorMapping, - ConnectorMappingFormValue, - ConverterMappingFormValue, - ConvertorType, - ConvertorTypeTranslationsMap, - DataConversionTranslationsMap, - DeviceConnectorMapping, - DeviceInfoType, - HelpLinkByMappingTypeMap, - MappingHintTranslationsMap, - MappingInfo, - MappingKeysAddKeyTranslationsMap, - MappingKeysDeleteKeyTranslationsMap, - MappingKeysNoKeysTextTranslationsMap, - MappingKeysPanelTitleTranslationsMap, - MappingKeysType, - MappingType, - MappingTypeTranslationsMap, - noLeadTrailSpacesRegex, - OPCUaSourceType, - QualityTypes, - QualityTypeTranslationsMap, - RequestMappingData, - RequestMappingFormValue, - RequestType, - RequestTypesTranslationsMap, - RpcMethod, - ServerSideRPCType, - SourceType, - SourceTypeTranslationsMap, - Timeseries -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Subject } from 'rxjs'; -import { startWith, takeUntil } from 'rxjs/operators'; -import { MatButton } from '@angular/material/button'; -import { TbPopoverService } from '@shared/components/popover.service'; -import { TranslateService } from '@ngx-translate/core'; -import { - MappingDataKeysPanelComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component'; - -@Component({ - selector: 'tb-mapping-dialog', - templateUrl: './mapping-dialog.component.html', - styleUrls: ['./mapping-dialog.component.scss'] -}) -export class MappingDialogComponent extends DialogComponent implements OnDestroy { - - mappingForm: UntypedFormGroup; - - MappingType = MappingType; - - qualityTypes = QualityTypes; - QualityTranslationsMap = QualityTypeTranslationsMap; - - convertorTypes: ConvertorType[] = Object.values(ConvertorType); - ConvertorTypeEnum = ConvertorType; - ConvertorTypeTranslationsMap = ConvertorTypeTranslationsMap; - - sourceTypes: SourceType[] = Object.values(SourceType); - OPCUaSourceTypes = Object.values(OPCUaSourceType) as Array; - OPCUaSourceTypesEnum = OPCUaSourceType; - sourceTypesEnum = SourceType; - SourceTypeTranslationsMap = SourceTypeTranslationsMap; - - requestTypes: RequestType[] = Object.values(RequestType); - RequestTypeEnum = RequestType; - RequestTypesTranslationsMap = RequestTypesTranslationsMap; - - DeviceInfoType = DeviceInfoType; - - ServerSideRPCType = ServerSideRPCType; - - MappingKeysType = MappingKeysType; - - MappingHintTranslationsMap = MappingHintTranslationsMap; - - MappingTypeTranslationsMap = MappingTypeTranslationsMap; - - DataConversionTranslationsMap = DataConversionTranslationsMap; - - HelpLinkByMappingTypeMap = HelpLinkByMappingTypeMap; - - keysPopupClosed = true; - - private destroy$ = new Subject(); - - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: MappingInfo, - public dialogRef: MatDialogRef, - private fb: FormBuilder, - private popoverService: TbPopoverService, - private renderer: Renderer2, - private viewContainerRef: ViewContainerRef, - private translate: TranslateService) { - super(store, router, dialogRef); - - this.createMappingForm(); - } - - get converterAttributes(): Array { - if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.attributes.map((value: Attribute) => value.key); - } - } - - get converterTelemetry(): Array { - if (this.converterType) { - return this.mappingForm.get('converter').get(this.converterType).value.timeseries.map((value: Timeseries) => value.key); - } - } - - get opcAttributes(): Array { - return this.mappingForm.get('attributes').value?.map((value: Attribute) => value.key) || []; - } - - get opcTelemetry(): Array { - return this.mappingForm.get('timeseries').value?.map((value: Timeseries) => value.key) || []; - } - - get opcRpcMethods(): Array { - return this.mappingForm.get('rpc_methods').value?.map((value: RpcMethod) => value.method) || []; - } - - get opcAttributesUpdates(): Array { - return this.mappingForm.get('attributes_updates')?.value?.map((value: AttributesUpdate) => value.key) || []; - } - - get converterType(): ConvertorType { - return this.mappingForm.get('converter').get('type').value; - } - - get customKeys(): Array { - return Object.keys(this.mappingForm.get('converter').get('custom').value.extensionConfig); - } - - get requestMappingType(): RequestType { - return this.mappingForm.get('requestType').value; - } - - get responseTimeoutErrorTooltip(): string { - const control = this.mappingForm.get('requestValue.serverSideRpc.responseTimeout'); - if (control.hasError('required')) { - return this.translate.instant('gateway.response-timeout-required'); - } else if (control.hasError('min')) { - return this.translate.instant('gateway.response-timeout-limits-error', {min: 1}); - } - return ''; - } - - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - } - - private createMappingForm(): void { - switch (this.data.mappingType) { - case MappingType.DATA: - this.mappingForm = this.fb.group({}); - this.createDataMappingForm(); - break; - case MappingType.REQUESTS: - this.mappingForm = this.fb.group({}); - this.createRequestMappingForm(); - break; - case MappingType.OPCUA: - this.createOPCUAMappingForm(); - } - } - - cancel(): void { - if (this.keysPopupClosed) { - this.dialogRef.close(null); - } - } - - add(): void { - if (this.mappingForm.valid) { - this.dialogRef.close(this.prepareMappingData()); - } - } - - manageKeys($event: Event, matButton: MatButton, keysType: MappingKeysType): void { - if ($event) { - $event.stopPropagation(); - } - const trigger = matButton._elementRef.nativeElement; - if (this.popoverService.hasPopover(trigger)) { - this.popoverService.hidePopover(trigger); - } else { - const group = this.data.mappingType !== MappingType.OPCUA ? this.mappingForm.get('converter').get(this.converterType) - : this.mappingForm; - - const keysControl = group.get(keysType); - const ctx: { [key: string]: any } = { - keys: keysControl.value, - keysType, - rawData: this.mappingForm.get('converter.type')?.value === ConvertorType.BYTES, - panelTitle: MappingKeysPanelTitleTranslationsMap.get(keysType), - addKeyTitle: MappingKeysAddKeyTranslationsMap.get(keysType), - deleteKeyTitle: MappingKeysDeleteKeyTranslationsMap.get(keysType), - noKeysText: MappingKeysNoKeysTextTranslationsMap.get(keysType) - }; - if (this.data.mappingType === MappingType.OPCUA) { - ctx.valueTypeKeys = Object.values(OPCUaSourceType); - ctx.valueTypeEnum = OPCUaSourceType; - ctx.valueTypes = SourceTypeTranslationsMap; - } - this.keysPopupClosed = false; - const dataKeysPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, MappingDataKeysPanelComponent, 'leftBottom', false, null, - ctx, - {}, - {}, {}, true); - dataKeysPanelPopover.tbComponentRef.instance.popover = dataKeysPanelPopover; - dataKeysPanelPopover.tbComponentRef.instance.keysDataApplied.pipe(takeUntil(this.destroy$)).subscribe((keysData) => { - dataKeysPanelPopover.hide(); - keysControl.patchValue(keysData); - keysControl.markAsDirty(); - }); - dataKeysPanelPopover.tbHideStart.pipe(takeUntil(this.destroy$)).subscribe(() => { - this.keysPopupClosed = true; - }); - } - } - - private prepareMappingData(): ConnectorMapping { - const formValue = this.mappingForm.value; - switch (this.data.mappingType) { - case MappingType.DATA: - const {converter, topicFilter, subscriptionQos} = formValue; - return { - topicFilter, - subscriptionQos, - converter: { - type: converter.type, - ...converter[converter.type] - } - }; - case MappingType.REQUESTS: - return { - requestType: formValue.requestType, - requestValue: formValue.requestValue[formValue.requestType] - }; - default: - return formValue; - } - } - - private getFormValueData(): ConnectorMappingFormValue { - if (this.data.value && Object.keys(this.data.value).length) { - switch (this.data.mappingType) { - case MappingType.DATA: - const {converter, topicFilter, subscriptionQos} = this.data.value; - return { - topicFilter, - subscriptionQos, - converter: { - type: converter.type, - [converter.type]: {...converter} - } - } as ConverterMappingFormValue; - case MappingType.REQUESTS: - return { - requestType: this.data.value.requestType, - requestValue: { - [this.data.value.requestType]: this.data.value.requestValue - } as Record - }; - default: - return this.data.value as DeviceConnectorMapping; - } - } - } - - private createDataMappingForm(): void { - this.mappingForm.addControl('topicFilter', - this.fb.control('', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)])); - this.mappingForm.addControl('subscriptionQos', this.fb.control(0)); - this.mappingForm.addControl('converter', this.fb.group({ - type: [ConvertorType.JSON, []], - json: this.fb.group({ - deviceInfo: [{}, []], - attributes: [[], []], - timeseries: [[], []] - }), - bytes: this.fb.group({ - deviceInfo: [{}, []], - attributes: [[], []], - timeseries: [[], []] - }), - custom: this.fb.group({ - extension: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - extensionConfig: [{}, []] - }), - })); - this.mappingForm.patchValue(this.getFormValueData()); - this.mappingForm.get('converter.type').valueChanges.pipe( - startWith(this.mappingForm.get('converter.type').value), - takeUntil(this.destroy$) - ).subscribe((value) => { - const converterGroup = this.mappingForm.get('converter'); - converterGroup.get('json').disable({emitEvent: false}); - converterGroup.get('bytes').disable({emitEvent: false}); - converterGroup.get('custom').disable({emitEvent: false}); - converterGroup.get(value).enable({emitEvent: false}); - }); - } - - private createRequestMappingForm(): void { - this.mappingForm.addControl('requestType', this.fb.control(RequestType.CONNECT_REQUEST, [])); - this.mappingForm.addControl('requestValue', this.fb.group({ - connectRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: [{}, []] - }), - disconnectRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: [{}, []] - }), - attributeRequests: this.fb.group({ - topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - deviceInfo: this.fb.group({ - deviceNameExpressionSource: [SourceType.MSG, []], - deviceNameExpression: ['', [Validators.required]], - }), - attributeNameExpressionSource: [SourceType.MSG, []], - attributeNameExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - retain: [false, []] - }), - attributeUpdates: this.fb.group({ - deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - attributeFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - retain: [true, []] - }), - serverSideRpc: this.fb.group({ - type: [ServerSideRPCType.TWO_WAY, []], - deviceNameFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - methodFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseTopicQoS: [0, []], - responseTimeout: [10000, [Validators.required, Validators.min(1)]], - }) - })); - this.mappingForm.get('requestType').valueChanges.pipe( - startWith(this.mappingForm.get('requestType').value), - takeUntil(this.destroy$) - ).subscribe((value) => { - const requestValueGroup = this.mappingForm.get('requestValue'); - requestValueGroup.get('connectRequests').disable({emitEvent: false}); - requestValueGroup.get('disconnectRequests').disable({emitEvent: false}); - requestValueGroup.get('attributeRequests').disable({emitEvent: false}); - requestValueGroup.get('attributeUpdates').disable({emitEvent: false}); - requestValueGroup.get('serverSideRpc').disable({emitEvent: false}); - requestValueGroup.get(value).enable(); - }); - this.mappingForm.get('requestValue.serverSideRpc.type').valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((value) => { - const requestValueGroup = this.mappingForm.get('requestValue.serverSideRpc'); - if (value === ServerSideRPCType.ONE_WAY) { - requestValueGroup.get('responseTopicExpression').disable({emitEvent: false}); - requestValueGroup.get('responseTopicQoS').disable({emitEvent: false}); - requestValueGroup.get('responseTimeout').disable({emitEvent: false}); - } else { - requestValueGroup.get('responseTopicExpression').enable({emitEvent: false}); - requestValueGroup.get('responseTopicQoS').enable({emitEvent: false}); - requestValueGroup.get('responseTimeout').enable({emitEvent: false}); - } - }); - this.mappingForm.patchValue(this.getFormValueData()); - } - - private createOPCUAMappingForm(): void { - this.mappingForm = this.fb.group({ - deviceNodeSource: [OPCUaSourceType.PATH, []], - deviceNodePattern: ['', [Validators.required]], - deviceInfo: [{}, []], - attributes: [[], []], - timeseries: [[], []], - rpc_methods: [[], []], - attributes_updates: [[], []] - }); - this.mappingForm.patchValue(this.getFormValueData()); - } -} 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 deleted file mode 100644 index a700dcc94b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ /dev/null @@ -1,323 +0,0 @@ - -
-
- -

{{ 'gateway.connectors' | translate }}

- - -
-
-
- -
- - - - {{ 'gateway.connectors-table-enabled' | translate }} - - - - - - - - {{ 'gateway.connectors-table-name' | translate }} - - {{ attribute.key }} - - - - - {{ 'gateway.connectors-table-type' | translate }} - - - {{ returnType(attribute) }} - - - - - {{ 'gateway.configuration' | translate }} - - -
- {{ isConnectorSynced(attribute) ? 'sync' : 'out of sync' }} -
-
-
- - - {{ 'gateway.connectors-table-status' | translate }} - - - - - - - - {{ 'gateway.connectors-table-actions' | translate }} - - -
- - - -
-
- - - - - - -
-
-
- - -
-
-
-
-
-
- {{ initialConnector?.type ? GatewayConnectorTypesTranslatesMap.get(initialConnector.type) : '' }} - {{ 'gateway.configuration' | translate }} - v{{connectorForm.get('configVersion').value}} -
- - - {{ 'gateway.basic' | translate }} - - - {{ 'gateway.advanced' | translate }} - - -
- - gateway.select-connector - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
- -
-
-
gateway.name
-
- - - - warning - - -
-
-
-
gateway.connectors-table-class
-
- - - -
-
-
-
gateway.connectors-table-key
-
- - - -
-
-
-
gateway.logs-configuration
-
- - - {{ 'gateway.enable-remote-logging' | translate }} - - -
-
-
gateway.remote-logging-level
-
- - - {{ logLevel }} - - -
-
-
-
- - - {{ 'gateway.send-change-data' | translate }} - - -
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss deleted file mode 100644 index 5d8c6aad5b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss +++ /dev/null @@ -1,156 +0,0 @@ -/** - * 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 '../../../../../../../scss/constants'; - -:host { - width: 100%; - height: 100%; - display: block; - overflow-x: auto; - padding: 0; - - .version-placeholder { - color: gray; - font-size: 12px - } - - .connector-container { - height: 100%; - width: 100%; - flex-direction: row; - @media #{$mat-lt-lg} { - flex-direction: column; - } - - & > section:not(.table-section) { - max-width: unset; - @media #{$mat-gt-md} { - max-width: 50%; - } - } - - .table-section { - min-height: 35vh; - overflow: hidden; - - .table-container { - overflow: auto; - } - } - - .flex { - flex: 1; - } - - .input-container { - height: auto; - } - - .section-container { - background-color: #fff; - } - } - - .mat-toolbar { - background: transparent; - color: rgba(0, 0, 0, .87) !important; - } - - .mat-mdc-slide-toggle { - margin: 0 8px; - } - - .status { - text-align: center; - border-radius: 16px; - font-weight: 500; - width: fit-content; - padding: 5px 15px; - &-sync { - background: rgba(25, 128, 56, .06); - color: rgb(25, 128, 56); - } - &-unsync { - background: rgba(203, 37, 48, .06); - color: rgb(203, 37, 48); - } - } - - mat-row { - cursor: pointer; - } - - .dot { - height: 12px; - width: 12px; - background-color: #bbb; - border-radius: 50%; - display: inline-block; - } - - .hasErrors { - background-color: rgb(203, 37, 48); - } - - .noErrors { - background-color: rgb(25, 128, 56); - } -} - -:host ::ng-deep { - .connector-container { - - .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { - height: 100%; - } - - .mat-mdc-tab-body.mat-mdc-tab-body-active { - position: absolute; - } - - .tb-form-row { - .fixed-title-width { - min-width: 120px; - width: 30%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - - .tb-add-new { - display: flex; - z-index: 999; - pointer-events: none; - background-color: #fff; - - button.connector { - height: auto; - padding-right: 12px; - font-size: 20px; - border-style: dashed; - border-width: 2px; - border-radius: 8px; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: center; - color: rgba(0, 0, 0, 0.38); - } - } - } -} - 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 deleted file mode 100644 index 00fd3e19c2..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ /dev/null @@ -1,897 +0,0 @@ -/// -/// 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 { - AfterViewInit, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, - OnDestroy, - ViewChild -} from '@angular/core'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, UntypedFormControl, ValidatorFn, Validators } from '@angular/forms'; -import { EntityId } from '@shared/models/id/entity-id'; -import { AttributeService } from '@core/http/attribute.service'; -import { TranslateService } from '@ngx-translate/core'; -import { forkJoin, Observable, of, Subject, Subscription } from 'rxjs'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { PageComponent } from '@shared/components/page.component'; -import { PageLink } from '@shared/models/page/page-link'; -import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; -import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { MatSort } from '@angular/material/sort'; -import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; -import { MatTableDataSource } from '@angular/material/table'; -import { ActionNotificationShow } from '@core/notification/notification.actions'; -import { DialogService } from '@core/services/dialog.service'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { camelCase, deepClone, isEqual, isString } from '@core/utils'; -import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { DatasourceType, widgetType } from '@shared/models/widget.models'; -import { UtilsService } from '@core/services/utils.service'; -import { EntityType } from '@shared/models/entity-type.models'; -import { - AddConnectorConfigData, - ConnectorBaseConfig, - ConnectorBaseInfo, - ConfigurationModes, - ConnectorType, - GatewayAttributeData, - GatewayConnector, - GatewayConnectorDefaultTypesTranslatesMap, - GatewayLogLevel, - noLeadTrailSpacesRegex, - ReportStrategyDefaultValue, - ReportStrategyType, -} from './gateway-widget.models'; -import { MatDialog } from '@angular/material/dialog'; -import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; -import { debounceTime, filter, switchMap, take, takeUntil, tap } from 'rxjs/operators'; -import { ErrorStateMatcher } from '@angular/material/core'; -import { PageData } from '@shared/models/page/page-data'; -import { - GatewayConnectorVersionMappingUtil -} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util'; -import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe'; - -export class ForceErrorStateMatcher implements ErrorStateMatcher { - isErrorState(control: FormControl | null): boolean { - return (control && control.invalid); - } -} - -@Component({ - selector: 'tb-gateway-connector', - templateUrl: './gateway-connectors.component.html', - providers: [{ provide: ErrorStateMatcher, useClass: ForceErrorStateMatcher }], - styleUrls: ['./gateway-connectors.component.scss'] -}) -export class GatewayConnectorComponent extends PageComponent implements AfterViewInit, OnDestroy { - - @Input() - ctx: WidgetContext; - @Input() - device: EntityId; - - @ViewChild('nameInput') nameInput: ElementRef; - @ViewChild(MatSort, {static: false}) sort: MatSort; - - readonly ConnectorType = ConnectorType; - readonly allowBasicConfig = new Set([ - ConnectorType.MQTT, - ConnectorType.OPCUA, - ConnectorType.MODBUS, - ]); - readonly gatewayLogLevel = Object.values(GatewayLogLevel); - readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; - readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap; - readonly ConnectorConfigurationModes = ConfigurationModes; - readonly ReportStrategyDefaultValue = ReportStrategyDefaultValue; - - pageLink: PageLink; - dataSource: MatTableDataSource; - connectorForm: FormGroup; - activeConnectors: Array; - mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC; - initialConnector: GatewayConnector; - basicConfigInitSubject = new Subject(); - - private gatewayVersion: string; - private isGatewayActive: boolean; - private inactiveConnectors: Array; - private attributeDataSource: AttributeDatasource; - private inactiveConnectorsDataSource: AttributeDatasource; - private serverDataSource: AttributeDatasource; - private activeData: Array = []; - private inactiveData: Array = []; - private sharedAttributeData: Array = []; - private basicConfigSub: Subscription; - private jsonConfigSub: Subscription; - private subscriptionOptions: WidgetSubscriptionOptions = { - callbacks: { - onDataUpdated: () => this.ctx.ngZone.run(() => { - this.onErrorsUpdated(); - }), - onDataUpdateError: (_, e) => this.ctx.ngZone.run(() => { - this.onDataUpdateError(e); - }) - } - }; - private destroy$ = new Subject(); - private subscription: IWidgetSubscription; - private attributeUpdateSubject = new Subject(); - - constructor(protected store: Store, - private fb: FormBuilder, - private translate: TranslateService, - private attributeService: AttributeService, - private dialogService: DialogService, - private dialog: MatDialog, - private telemetryWsService: TelemetryWebsocketService, - private zone: NgZone, - private utils: UtilsService, - private isLatestVersionConfig: LatestVersionConfigPipe, - private cd: ChangeDetectorRef) { - super(store); - - this.initDataSources(); - this.initConnectorForm(); - this.observeAttributeChange(); - } - - ngAfterViewInit(): void { - this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = this.getSortingDataAccessor(); - this.ctx.$scope.gatewayConnectors = this; - - this.loadConnectors(); - this.loadGatewayState(); - this.observeModeChange(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - } - - onSaveConnector(): void { - this.saveConnector(this.getUpdatedConnectorData(this.connectorForm.value), false); - } - - private saveConnector(connector: GatewayConnector, isNew = true): void { - const scope = (isNew || this.activeConnectors.includes(this.initialConnector.name)) - ? AttributeScope.SHARED_SCOPE - : AttributeScope.SERVER_SCOPE; - - forkJoin(this.getEntityAttributeTasks(connector, scope)).pipe(take(1)).subscribe(_ => { - this.showToast(isNew - ? this.translate.instant('gateway.connector-created') - : this.translate.instant('gateway.connector-updated') - ); - this.initialConnector = connector; - this.updateData(true); - this.connectorForm.markAsPristine(); - }); - } - - private getEntityAttributeTasks(value: GatewayConnector, scope: AttributeScope): Observable[] { - const tasks = []; - const attributesToSave = [{ key: value.name, value }]; - const attributesToDelete = []; - const shouldAddToConnectorsList = !this.activeConnectors.includes(value.name) && scope === AttributeScope.SHARED_SCOPE - || !this.inactiveConnectors.includes(value.name) && scope === AttributeScope.SERVER_SCOPE; - const isNewConnector = this.initialConnector && this.initialConnector.name !== value.name; - - if (isNewConnector) { - attributesToDelete.push({ key: this.initialConnector.name }); - this.removeConnectorFromList(this.initialConnector.name, true); - this.removeConnectorFromList(this.initialConnector.name, false); - } - - if (shouldAddToConnectorsList) { - if (scope === AttributeScope.SHARED_SCOPE) { - this.activeConnectors.push(value.name); - } else { - this.inactiveConnectors.push(value.name); - } - } - - if (isNewConnector || shouldAddToConnectorsList) { - tasks.push(this.getSaveEntityAttributesTask(scope)); - } - - tasks.push(this.attributeService.saveEntityAttributes(this.device, scope, attributesToSave)); - - if (attributesToDelete.length) { - tasks.push(this.attributeService.deleteEntityAttributes(this.device, scope, attributesToDelete)); - } - - return tasks; - } - - private getSaveEntityAttributesTask(scope: AttributeScope): Observable { - const key = scope === AttributeScope.SHARED_SCOPE ? 'active_connectors' : 'inactive_connectors'; - const value = scope === AttributeScope.SHARED_SCOPE ? this.activeConnectors : this.inactiveConnectors; - - return this.attributeService.saveEntityAttributes(this.device, scope, [{ key, value }]); - } - - private removeConnectorFromList(connectorName: string, isActive: boolean): void { - const list = isActive? this.activeConnectors : this.inactiveConnectors; - const index = list.indexOf(connectorName); - if (index !== -1) { - list.splice(index, 1); - } - } - - private getUpdatedConnectorData(connector: GatewayConnector): GatewayConnector { - const value = {...connector }; - value.configuration = `${camelCase(value.name)}.json`; - delete value.basicConfig; - - if (value.type !== ConnectorType.GRPC) { - delete value.key; - } - if (value.type !== ConnectorType.CUSTOM) { - delete value.class; - } - - if (value.type === ConnectorType.MODBUS && this.isLatestVersionConfig.transform(value.configVersion)) { - if (!value.reportStrategy) { - value.reportStrategy = { - type: ReportStrategyType.OnReportPeriod, - reportPeriod: ReportStrategyDefaultValue.Connector - }; - delete value.sendDataOnlyOnChange; - } - } - - if (this.gatewayVersion && !value.configVersion) { - value.configVersion = this.gatewayVersion; - } - - value.ts = Date.now(); - - return value; - } - - private updateData(reload: boolean = false): void { - this.pageLink.sortOrder.property = this.sort.active; - this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; - this.attributeDataSource.loadAttributes(this.device, AttributeScope.CLIENT_SCOPE, this.pageLink, reload).subscribe(data => { - this.activeData = data.data.filter(value => this.activeConnectors.includes(value.key)); - this.combineData(); - this.generateSubscription(); - this.setClientData(data); - }); - this.inactiveConnectorsDataSource.loadAttributes(this.device, AttributeScope.SHARED_SCOPE, this.pageLink, reload).subscribe(data => { - this.sharedAttributeData = data.data.filter(value => this.activeConnectors.includes(value.key)); - this.combineData(); - }); - this.serverDataSource.loadAttributes(this.device, AttributeScope.SERVER_SCOPE, this.pageLink, reload).subscribe(data => { - this.inactiveData = data.data.filter(value => this.inactiveConnectors.includes(value.key)); - this.combineData(); - }); - } - - isConnectorSynced(attribute: GatewayAttributeData): boolean { - const connectorData = attribute.value; - if (!connectorData.ts || attribute.skipSync || !this.isGatewayActive) { - return false; - } - const clientIndex = this.activeData.findIndex(data => { - const sharedData = typeof data.value === 'string' ? JSON.parse(data.value) : data.value; - return sharedData.name === connectorData.name; - }); - if (clientIndex === -1) { - return false; - } - const sharedIndex = this.sharedAttributeData.findIndex(data => { - const sharedData = data.value; - const hasSameName = sharedData.name === connectorData.name; - const hasEmptyConfig = isEqual(sharedData.configurationJson, {}) && hasSameName; - const hasSameConfig = this.hasSameConfig(sharedData.configurationJson, connectorData.configurationJson); - const isRecentlyCreated = sharedData.ts && sharedData.ts <= connectorData.ts; - return hasSameName && isRecentlyCreated && (hasSameConfig || hasEmptyConfig); - }); - return sharedIndex !== -1; - } - - private hasSameConfig(sharedDataConfigJson: ConnectorBaseInfo, connectorDataConfigJson: ConnectorBaseInfo): boolean { - const { name, id, enableRemoteLogging, logLevel, reportStrategy, configVersion, ...sharedDataConfig } = sharedDataConfigJson; - const { - name: connectorName, - id: connectorId, - enableRemoteLogging: connectorEnableRemoteLogging, - logLevel: connectorLogLevel, - reportStrategy: connectorReportStrategy, - configVersion: connectorConfigVersion, - ...connectorConfig - } = connectorDataConfigJson; - - return isEqual(sharedDataConfig, connectorConfig); - } - - private combineData(): void { - const combinedData = [ - ...this.activeData, - ...this.inactiveData, - ...this.sharedAttributeData - ]; - - const latestData = combinedData.reduce((acc, attribute) => { - const existingItemIndex = acc.findIndex(item => item.key === attribute.key); - - if (existingItemIndex === -1) { - acc.push(attribute); - } else if ( - attribute.lastUpdateTs > acc[existingItemIndex].lastUpdateTs && - !this.isConnectorSynced(acc[existingItemIndex]) - ) { - acc[existingItemIndex] = { ...attribute, skipSync: true }; - } - - return acc; - }, []); - - this.dataSource.data = latestData.map(attribute => ({ - ...attribute, - value: typeof attribute.value === 'string' ? JSON.parse(attribute.value) : attribute.value - })); - } - - private clearOutConnectorForm(): void { - this.initialConnector = null; - this.connectorForm.setValue({ - mode: ConfigurationModes.BASIC, - name: '', - type: ConnectorType.MQTT, - sendDataOnlyOnChange: false, - enableRemoteLogging: false, - logLevel: GatewayLogLevel.INFO, - key: 'auto', - class: '', - configuration: '', - configurationJson: {}, - basicConfig: {}, - configVersion: '', - reportStrategy: [{ value: {}, disabled: true }], - }, {emitEvent: false}); - this.connectorForm.markAsPristine(); - } - - selectConnector($event: Event, attribute: GatewayAttributeData): void { - if ($event) { - $event.stopPropagation(); - } - const connector = attribute.value; - if (connector?.name !== this.initialConnector?.name) { - this.confirmConnectorChange().subscribe((result) => { - if (result) { - this.setFormValue(connector); - } - }); - } - } - - isSameConnector(attribute: GatewayAttributeData): boolean { - if (!this.initialConnector) { - return false; - } - const connector = attribute.value; - return this.initialConnector.name === connector.name; - } - - showToast(message: string): void { - this.store.dispatch(new ActionNotificationShow( - { - message, - type: 'success', - duration: 1000, - verticalPosition: 'top', - horizontalPosition: 'left', - target: 'dashboardRoot', - forceDismiss: true - })); - } - - returnType(attribute: GatewayAttributeData): string { - const value = attribute.value; - return this.GatewayConnectorTypesTranslatesMap.get(value.type); - } - - deleteConnector(attribute: GatewayAttributeData, $event: Event): void { - $event?.stopPropagation(); - - const title = `Delete connector \"${attribute.key}\"?`; - const content = `All connector data will be deleted.`; - - this.dialogService.confirm(title, content, 'Cancel', 'Delete').pipe( - take(1), - switchMap((result) => { - if (!result) { - return; - } - const tasks: Array> = []; - const scope = this.activeConnectors.includes(attribute.value?.name) ? - AttributeScope.SHARED_SCOPE : - AttributeScope.SERVER_SCOPE; - tasks.push(this.attributeService.deleteEntityAttributes(this.device, scope, [attribute])); - this.removeConnectorFromList(attribute.key, true); - this.removeConnectorFromList(attribute.key, false); - tasks.push(this.getSaveEntityAttributesTask(scope)); - - return forkJoin(tasks); - }) - ).subscribe(() => { - if (this.initialConnector ? this.initialConnector.name === attribute.key : true) { - this.clearOutConnectorForm(); - this.cd.detectChanges(); - this.connectorForm.disable(); - } - this.updateData(true); - }); - } - - connectorLogs(attribute: GatewayAttributeData, $event: Event): void { - if ($event) { - $event.stopPropagation(); - } - const params = deepClone(this.ctx.stateController.getStateParams()); - params.connector_logs = attribute; - params.targetEntityParamName = 'connector_logs'; - this.ctx.stateController.openState('connector_logs', params); - } - - connectorRpc(attribute: GatewayAttributeData, $event: Event): void { - if ($event) { - $event.stopPropagation(); - } - const params = deepClone(this.ctx.stateController.getStateParams()); - params.connector_rpc = attribute; - params.targetEntityParamName = 'connector_rpc'; - this.ctx.stateController.openState('connector_rpc', params); - } - - - onEnableConnector(attribute: GatewayAttributeData): void { - attribute.value.ts = new Date().getTime(); - - this.updateActiveConnectorKeys(attribute.key); - - this.attributeUpdateSubject.next(attribute); - } - - getErrorsCount(attribute: GatewayAttributeData): string { - const connectorName = attribute.key; - const connector = this.subscription && this.subscription.data - .find(data => data && data.dataKey.name === `${connectorName}_ERRORS_COUNT`); - return (connector && this.activeConnectors.includes(connectorName)) ? (connector.data[0][1] || 0) : 'Inactive'; - } - - onAddConnector(event?: Event): void { - event?.stopPropagation(); - - this.confirmConnectorChange() - .pipe( - take(1), - filter(Boolean), - switchMap(() => this.openAddConnectorDialog()), - filter(Boolean), - ) - .subscribe(connector => this.addConnector(connector)); - } - - private addConnector(connector: GatewayConnector): void { - if (this.connectorForm.disabled) { - this.connectorForm.enable(); - } - if (!connector.configurationJson) { - connector.configurationJson = {} as ConnectorBaseConfig; - } - if (this.gatewayVersion && !connector.configVersion) { - connector.configVersion = this.gatewayVersion; - } - connector.basicConfig = connector.configurationJson; - this.initialConnector = connector; - - const previousType = this.connectorForm.get('type').value; - - this.setInitialConnectorValues(connector); - - this.saveConnector(this.getUpdatedConnectorData(connector)); - - if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) { - this.patchBasicConfigConnector(connector); - } else { - this.basicConfigInitSubject.pipe(take(1)).subscribe(() => { - this.patchBasicConfigConnector(connector); - }); - } - } - - private setInitialConnectorValues(connector: GatewayConnector): void { - const {basicConfig, mode, ...initialConnector} = connector; - this.toggleReportStrategy(connector.type); - this.connectorForm.get('mode').setValue(this.allowBasicConfig.has(connector.type) - ? connector.mode ?? ConfigurationModes.BASIC - : null, {emitEvent: false} - ); - this.connectorForm.patchValue(initialConnector, {emitEvent: false}); - } - - private openAddConnectorDialog(): Observable { - return this.ctx.ngZone.run(() => - this.dialog.open(AddConnectorDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - dataSourceData: this.dataSource.data, - gatewayVersion: this.gatewayVersion, - } - }).afterClosed() - ); - } - - uniqNameRequired(): ValidatorFn { - return (control: UntypedFormControl) => { - const newName = control.value?.trim().toLowerCase(); - const isDuplicate = this.dataSource.data.some(connectorAttr => connectorAttr.value.name.toLowerCase() === newName); - const isSameAsInitial = this.initialConnector?.name.toLowerCase() === newName; - - if (isDuplicate && !isSameAsInitial) { - return { duplicateName: { valid: false } }; - } - - return null; - }; - } - - private initDataSources(): void { - const sortOrder: SortOrder = {property: 'key', direction: Direction.ASC}; - this.pageLink = new PageLink(1000, 0, null, sortOrder); - this.attributeDataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate); - this.inactiveConnectorsDataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate); - this.serverDataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate); - this.dataSource = new MatTableDataSource([]); - } - - private initConnectorForm(): void { - this.connectorForm = this.fb.group({ - mode: [ConfigurationModes.BASIC], - name: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(noLeadTrailSpacesRegex)]], - type: ['', [Validators.required]], - enableRemoteLogging: [false], - logLevel: ['', [Validators.required]], - sendDataOnlyOnChange: [false], - key: ['auto'], - class: [''], - configuration: [''], - configurationJson: [{}, [Validators.required]], - basicConfig: [{}], - configVersion: [''], - reportStrategy: [{ value: {}, disabled: true }], - }); - this.connectorForm.disable(); - } - - private getSortingDataAccessor(): (data: GatewayAttributeData, sortHeaderId: string) => string | number { - return (data: GatewayAttributeData, sortHeaderId: string) => { - 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]; - } - }; - } - - private loadConnectors(): void { - if (!this.device || this.device.id === NULL_UUID) { - return; - } - - forkJoin([ - this.attributeService.getEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, ['active_connectors']), - this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, ['inactive_connectors']), - this.attributeService.getEntityAttributes(this.device, AttributeScope.CLIENT_SCOPE, ['Version']) - ]).pipe(takeUntil(this.destroy$)).subscribe(attributes => { - this.activeConnectors = this.parseConnectors(attributes[0]); - this.inactiveConnectors = this.parseConnectors(attributes[1]); - this.gatewayVersion = attributes[2][0]?.value; - - this.updateData(true); - }); - } - - private loadGatewayState(): void { - this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE) - .pipe(takeUntil(this.destroy$)) - .subscribe((attributes: AttributeData[]) => { - - const active = attributes.find(data => data.key === 'active').value; - const lastDisconnectedTime = attributes.find(data => data.key === 'lastDisconnectTime')?.value; - const lastConnectedTime = attributes.find(data => data.key === 'lastConnectTime')?.value; - - this.isGatewayActive = this.getGatewayStatus(active, lastConnectedTime, lastDisconnectedTime); - }); - } - - private parseConnectors(attribute: GatewayAttributeData[]): string[] { - const connectors = attribute?.[0]?.value || []; - return isString(connectors) ? JSON.parse(connectors) : connectors; - } - - private observeModeChange(): void { - this.connectorForm.get('mode').valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.connectorForm.get('mode').markAsPristine(); - }); - } - - private observeAttributeChange(): void { - this.attributeUpdateSubject.pipe( - debounceTime(300), - tap((attribute: GatewayAttributeData) => 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: GatewayAttributeData): void { - forkJoin(this.getAttributeExecutionTasks(attribute)) - .pipe( - take(1), - tap(() => this.updateData(true)), - takeUntil(this.destroy$), - ) - .subscribe(); - } - - private getAttributeExecutionTasks(attribute: GatewayAttributeData): 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; - if (exceptionData.message) { - errorText += ': ' + exceptionData.message; - } - console.error(errorText); - } - - private onErrorsUpdated(): void { - this.cd.detectChanges(); - } - - private onDataUpdated(): void { - const dataSources = this.ctx.defaultSubscription.data; - - const active = dataSources.find(data => data.dataKey.name === 'active').data[0][1]; - const lastDisconnectedTime = dataSources.find(data => data.dataKey.name === 'lastDisconnectTime').data[0][1]; - const lastConnectedTime = dataSources.find(data => data.dataKey.name === 'lastConnectTime').data[0][1]; - - this.isGatewayActive = this.getGatewayStatus(active, lastConnectedTime, lastDisconnectedTime); - - this.cd.detectChanges(); - } - - private getGatewayStatus(active: boolean, lastConnectedTime: number, lastDisconnectedTime: number): boolean { - if (!active) { - return false; - } - return !lastDisconnectedTime || lastConnectedTime > lastDisconnectedTime; - } - - private generateSubscription(): void { - if (this.subscription) { - this.subscription.unsubscribe(); - } - if (this.device) { - const subscriptionInfo = [{ - type: DatasourceType.entity, - entityType: EntityType.DEVICE, - entityId: this.device.id, - entityName: 'Gateway', - timeseries: [] - }]; - this.dataSource.data.forEach(value => { - subscriptionInfo[0].timeseries.push({name: `${value.key}_ERRORS_COUNT`, label: `${value.key}_ERRORS_COUNT`}); - }); - this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo, this.subscriptionOptions, - false, true).subscribe(subscription => { - this.subscription = subscription; - }); - } - } - - private createBasicConfigWatcher(): void { - if (this.basicConfigSub) { - this.basicConfigSub.unsubscribe(); - } - this.basicConfigSub = this.connectorForm.get('basicConfig').valueChanges.pipe( - filter(() => !!this.initialConnector), - takeUntil(this.destroy$) - ).subscribe((config) => { - const configJson = this.connectorForm.get('configurationJson'); - const type = this.connectorForm.get('type').value; - const mode = this.connectorForm.get('mode').value; - if (!isEqual(config, configJson?.value) && this.allowBasicConfig.has(type) && mode === ConfigurationModes.BASIC) { - const newConfig = {...configJson.value, ...config}; - this.connectorForm.get('configurationJson').patchValue(newConfig, {emitEvent: false}); - } - }); - } - - private createJsonConfigWatcher(): void { - if (this.jsonConfigSub) { - this.jsonConfigSub.unsubscribe(); - } - this.jsonConfigSub = this.connectorForm.get('configurationJson').valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe((config) => { - const basicConfig = this.connectorForm.get('basicConfig'); - const type = this.connectorForm.get('type').value; - const mode = this.connectorForm.get('mode').value; - if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConfigurationModes.ADVANCED) { - this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false}); - } - }); - } - - private confirmConnectorChange(): Observable { - if (this.initialConnector && this.connectorForm.dirty) { - return this.dialogService.confirm( - this.translate.instant('gateway.change-connector-title'), - this.translate.instant('gateway.change-connector-text'), - this.translate.instant('action.no'), - this.translate.instant('action.yes'), - true - ); - } - return of(true); - } - - private setFormValue(connector: GatewayConnector): void { - if (this.connectorForm.disabled) { - this.connectorForm.enable(); - } - - const connectorState = GatewayConnectorVersionMappingUtil.getConfig({ - configuration: '', - key: 'auto', - configurationJson: {} as ConnectorBaseConfig, - ...connector, - }, this.gatewayVersion); - - if (this.gatewayVersion && !connectorState.configVersion) { - connectorState.configVersion = this.gatewayVersion; - } - - connectorState.basicConfig = connectorState.configurationJson; - this.initialConnector = connectorState; - this.updateConnector(connectorState); - } - - private updateConnector(connector: GatewayConnector): void { - this.jsonConfigSub?.unsubscribe(); - switch (connector.type) { - case ConnectorType.MQTT: - case ConnectorType.OPCUA: - case ConnectorType.MODBUS: - this.updateBasicConfigConnector(connector); - break; - default: - this.connectorForm.patchValue({...connector, mode: null}); - this.connectorForm.markAsPristine(); - this.createJsonConfigWatcher(); - } - } - - private updateBasicConfigConnector(connector: GatewayConnector): void { - this.basicConfigSub?.unsubscribe(); - const previousType = this.connectorForm.get('type').value; - this.setInitialConnectorValues(connector); - - if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) { - this.patchBasicConfigConnector(connector); - } else { - this.basicConfigInitSubject.asObservable().pipe(take(1)).subscribe(() => { - this.patchBasicConfigConnector(connector); - }); - } - } - - private patchBasicConfigConnector(connector: GatewayConnector): void { - this.connectorForm.patchValue(connector, {emitEvent: false}); - this.connectorForm.markAsPristine(); - this.createBasicConfigWatcher(); - this.createJsonConfigWatcher(); - } - - private toggleReportStrategy(type: ConnectorType): void { - const reportStrategyControl = this.connectorForm.get('reportStrategy'); - if (type === ConnectorType.MODBUS) { - reportStrategyControl.enable({emitEvent: false}); - } else { - reportStrategyControl.disable({emitEvent: false}); - } - } - - private setClientData(data: PageData): void { - if (this.initialConnector) { - const clientConnectorData = data.data.find(attr => attr.key === this.initialConnector.name); - if (clientConnectorData) { - clientConnectorData.value = typeof clientConnectorData.value === 'string' ? - JSON.parse(clientConnectorData.value) : clientConnectorData.value; - - if (this.isConnectorSynced(clientConnectorData) && clientConnectorData.value.configurationJson) { - this.setFormValue({...clientConnectorData.value, mode: this.connectorForm.get('mode').value ?? clientConnectorData.value.mode}); - } - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html deleted file mode 100644 index 7daa490f01..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - -
{{ 'gateway.thingsboard' | translate | uppercase }}
-
-
- - -
- - {{'gateway.security-type' | translate }} - - - {{ securityType.value.toString() | translate }} - - - -
-
- - {{ 'gateway.thingsboard-host' | translate }} - - - gateway.thingsboard-host-required - - - - {{ 'gateway.thingsboard-port' | translate }} - - - gateway.thingsboard-port-required - - - gateway.thingsboard-port-min - - - gateway.thingsboard-port-max - - - gateway.thingsboard-port-pattern - - -
- -
- - {{ 'gateway.tls-path-ca-certificate' | translate }} - - - - {{ 'gateway.tls-path-private-key' | translate }} - - - - {{ 'gateway.tls-path-client-certificate' | translate }} - - -
- - {{ 'gateway.remote' | translate }} - -
- - {{'gateway.remote-logging-level' | translate }} - - - {{ logLevel }} - - - - - - {{ 'gateway.path-logs' | translate }} - - - gateway.path-logs-required - - -
- -
- - - - -
{{ 'gateway.storage' | translate | uppercase }}
-
-
- -
- - {{'gateway.storage-type' | translate }} - - - {{ storageType.value.toString() | translate}} - - - - -
- - {{ 'gateway.storage-pack-size' | translate }} - - - gateway.storage-pack-size-required - - - gateway.storage-pack-size-min - - - gateway.storage-pack-size-pattern - - - - - - {{ (gatewayConfigurationGroup.get('storageType').value !== 'file' ? 'gateway.storage-max-records' : 'gateway.storage-max-file-records') | translate}} - - - - gateway.storage-max-records-required - - - gateway.storage-max-records-min - - - gateway.storage-max-records-pattern - - -
- -
- - {{ 'gateway.storage-max-files' | translate }} - - - gateway.storage-max-files-required - - - gateway.storage-max-files-min - - - gateway.storage-max-files-pattern - - - - - {{ 'gateway.storage-path' | translate }} - - - gateway.storage-path-required - - -
-
-
- - - - -
{{ 'gateway.connectors-config' | translate | uppercase }}
-
-
- -
-
-
-
- -
-
- - {{'gateway.connector-type' | translate }} - - - {{ connectorType }} - - - - gateway.connector-type-required - - - - - {{ 'gateway.connector-name' | translate }} - - - gateway.connector-name-required - - -
-
- - -
-
-
- {{'gateway.no-connectors' | translate}} -
- -
-
-
-
-
- - - -
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.scss deleted file mode 100644 index 3d28120fa3..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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{ - .gateway-form { - height: 100%; - padding: 5px; - background-color: transparent; - overflow-y: auto; - overflow-x: hidden; - - .form-action-buttons{ - padding-top: 8px; - } - - .gateway-config { - .no-data-found { - position: relative; - display: flex; - height: 40px; - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts deleted file mode 100644 index 4f8b1ee34b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts +++ /dev/null @@ -1,426 +0,0 @@ -/// -/// 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 { Component, ElementRef, Inject, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, NgForm, Validators } from '@angular/forms'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { UtilsService } from '@core/services/utils.service'; -import { - CONFIGURATION_ATTRIBUTE, - CONFIGURATION_DRAFT_ATTRIBUTE, - ConnectorType, - createFormConfig, - CURRENT_CONFIGURATION_ATTRIBUTE, - DEFAULT_CONNECTOR, - gatewayConfigJSON, - GatewayFormConnectorModel, - GatewayFormModels, - GatewayLogLevel, - generateConnectorConfigFiles, - generateLogConfigFile, - generateYAMLConfigFile, - getDraftConnectorsJSON, - getEntityId, - REMOTE_LOGGING_LEVEL_ATTRIBUTE, - SecurityType, - SecurityTypeTranslationMap, - StorageType, - StorageTypeTranslationMap, - ValidateJSON, - WidgetSetting -} from './gateway-form.models'; -import { WINDOW } from '@core/services/window.service'; -import { MatDialog } from '@angular/material/dialog'; -import { - JsonObjectEditDialogComponent, - JsonObjectEditDialogData -} from '@shared/components/dialog/json-object-edit-dialog.component'; -import { TranslateService } from '@ngx-translate/core'; -import { DeviceService } from '@core/http/device.service'; -import { AttributeService } from '@core/http/attribute.service'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { forkJoin, Observable } from 'rxjs'; -import { tap } from 'rxjs/operators'; -import { ImportExportService } from '@shared/import-export/import-export.service'; - -// @dynamic -@Component({ - selector: 'tb-gateway-form', - templateUrl: './gateway-form.component.html', - styleUrls: ['./gateway-form.component.scss'] -}) - -export class GatewayFormComponent extends PageComponent implements OnInit, OnDestroy { - constructor( - protected store: Store, - private elementRef: ElementRef, - private utils: UtilsService, - private ngZone: NgZone, - private fb: UntypedFormBuilder, - @Inject(WINDOW) private window: Window, - private dialog: MatDialog, - private translate: TranslateService, - private deviceService: DeviceService, - private attributeService: AttributeService, - private importExport: ImportExportService - ) { - super(store); - } - - get connectors(): UntypedFormArray { - return this.gatewayConfigurationGroup.get('connectors') as UntypedFormArray; - } - - @ViewChild('formContainer', {static: true}) formContainerRef: ElementRef; - @ViewChild('gatewayConfigurationForm', {static: true}) multipleInputForm: NgForm; - - private successfulSaved: string; - private gatewayNameExists: string; - private archiveFileName: string; - private formResize$: ResizeObserver; - private subscribeStorageType$: any; - private subscribeGateway$: any; - - alignment = 'row'; - layoutGap = '5px'; - gatewayType: string; - gatewayConfigurationGroup: UntypedFormGroup; - securityTypes = SecurityTypeTranslationMap; - gatewayLogLevels = Object.keys(GatewayLogLevel).map(itm => GatewayLogLevel[itm]); - connectorTypes = Object.keys(ConnectorType); - storageTypes = StorageTypeTranslationMap; - - toastTargetId = 'gateway-configuration-widget' + this.utils.guid(); - - @Input() - ctx: WidgetContext; - - @Input() - isStateForm: boolean; - - isReadOnlyForm = false; - deviceNameForm: string; - - ngOnInit(): void { - this.initWidgetSettings(this.ctx.settings); - if (this.ctx.datasources && this.ctx.datasources.length) { - this.deviceNameForm = this.ctx.datasources[0].name; - } - - this.buildForm(); - this.ctx.updateWidgetParams(); - this.formResize$ = new ResizeObserver(() => { - this.resize(); - }); - this.formResize$.observe(this.formContainerRef.nativeElement); - } - - ngOnDestroy(): void { - if (this.formResize$) { - this.formResize$.disconnect(); - } - this.subscribeGateway$.unsubscribe(); - this.subscribeStorageType$.unsubscribe(); - } - - private initWidgetSettings(settings: WidgetSetting): void { - let widgetTitle; - if (settings.gatewayTitle && settings.gatewayTitle.length) { - widgetTitle = this.utils.customTranslation(settings.gatewayTitle, settings.gatewayTitle); - } else { - widgetTitle = this.translate.instant('gateway.gateway'); - } - this.ctx.widgetTitle = widgetTitle; - this.isReadOnlyForm = (settings.readOnly) ? settings.readOnly : false; - - this.archiveFileName = settings.archiveFileName?.length ? settings.archiveFileName : 'gatewayConfiguration'; - this.gatewayType = settings.gatewayType?.length ? settings.gatewayType : 'Gateway'; - this.gatewayNameExists = this.utils.customTranslation(settings.gatewayNameExists, settings.gatewayNameExists) || this.translate.instant('gateway.gateway-exists'); - this.successfulSaved = this.utils.customTranslation(settings.successfulSave, settings.successfulSave) || this.translate.instant('gateway.gateway-saved'); - - this.updateWidgetDisplaying(); - } - - private resize(): void { - this.ngZone.run(() => { - this.updateWidgetDisplaying(); - this.ctx.detectChanges(); - }); - } - - private updateWidgetDisplaying(): void { - if(this.ctx.$container && this.ctx.$container[0].offsetWidth <= 425){ - this.layoutGap = '0'; - this.alignment = 'column'; - } else { - this.layoutGap = '5px'; - this.alignment = 'row'; - } - } - - private saveAttribute(attributeName: string, attributeValue: string, attributeScope: AttributeScope): Observable { - const gatewayId = this.gatewayConfigurationGroup.get('gateway').value; - const attributes: AttributeData = { - key: attributeName, - value: attributeValue - }; - return this.attributeService.saveEntityAttributes(getEntityId(gatewayId), attributeScope, [attributes]); - } - - private createConnector(setting: GatewayFormConnectorModel = DEFAULT_CONNECTOR): void { - this.connectors.push(this.fb.group({ - enabled: [setting.enabled], - configType: [setting.configType, [Validators.required]], - name: [setting.name, [Validators.required]], - config: [setting.config, [Validators.nullValidator, ValidateJSON]] - })); - } - - private getFormField(name: string): AbstractControl { - return this.gatewayConfigurationGroup.get(name); - } - - private buildForm(): void { - this.gatewayConfigurationGroup = this.fb.group({ - gateway: [null, []], - accessToken: [null, [Validators.required]], - securityType: [SecurityType.accessToken], - host: [this.window.location.hostname, [Validators.required]], - port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], - remoteConfiguration: [true], - caCertPath: ['/etc/thingsboard-gateway/ca.pem'], - privateKeyPath: ['/etc/thingsboard-gateway/privateKey.pem'], - certPath: ['/etc/thingsboard-gateway/certificate.pem'], - remoteLoggingLevel: [GatewayLogLevel.debug], - remoteLoggingPathToLogs: ['./logs/', [Validators.required]], - storageType: [StorageType.memory], - readRecordsCount: [100, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - maxRecordsCount: [10000, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - maxFilesCount: [5, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - dataFolderPath: ['./data/', [Validators.required]], - connectors: this.fb.array([]) - }); - - if (this.isReadOnlyForm) { - this.gatewayConfigurationGroup.disable({emitEvent: false}); - } - - this.subscribeStorageType$ = this.getFormField('storageType').valueChanges.subscribe((value: StorageType) => { - if (value === StorageType.memory) { - this.getFormField('maxFilesCount').disable(); - this.getFormField('dataFolderPath').disable(); - } else { - this.getFormField('maxFilesCount').enable(); - this.getFormField('dataFolderPath').enable(); - } - }); - - this.subscribeGateway$ = this.getFormField('gateway').valueChanges.subscribe((gatewayId: string) => { - if (gatewayId !== null) { - forkJoin([ - this.deviceService.getDeviceCredentials(gatewayId).pipe(tap(deviceCredential => { - this.getFormField('accessToken').patchValue(deviceCredential.credentialsId); - })), - ...this.getAttributes(gatewayId)]).subscribe(() => { - this.gatewayConfigurationGroup.markAsPristine(); - this.ctx.detectChanges(); - }); - } else { - this.getFormField('accessToken').patchValue(''); - } - }) - } - - gatewayExist(): void { - this.ctx.showErrorToast(this.gatewayNameExists, 'top', 'left', this.toastTargetId); - } - - exportConfig(): void { - const gatewayConfiguration: GatewayFormModels = this.gatewayConfigurationGroup.value; - const filesZip: any = {}; - filesZip['tb_gateway.yaml'] = generateYAMLConfigFile(gatewayConfiguration); - generateConnectorConfigFiles(filesZip, gatewayConfiguration.connectors); - generateLogConfigFile(filesZip, gatewayConfiguration.remoteLoggingLevel, gatewayConfiguration.remoteLoggingPathToLogs); - this.importExport.exportJSZip(filesZip, this.archiveFileName); - this.saveAttribute( - REMOTE_LOGGING_LEVEL_ATTRIBUTE, - this.gatewayConfigurationGroup.value.remoteLoggingLevel.toUpperCase(), - AttributeScope.SHARED_SCOPE); - } - - addNewConnector(): void { - this.createConnector(); - } - - removeConnector(index: number): void { - if (index > -1) { - this.connectors.removeAt(index); - this.connectors.markAsDirty(); - } - } - - openConfigDialog($event: Event, index: number, config: object, type: string): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - this.dialog.open(JsonObjectEditDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonValue: config, - required: true, - title: this.translate.instant('gateway.title-connectors-json', {typeName: type}) - } - }).afterClosed().subscribe( - (res) => { - if (res) { - this.connectors.at(index).get('config').patchValue(res); - this.ctx.detectChanges(); - } - } - ); - } - - private createConnectorName(connectors: GatewayFormConnectorModel[], name: string, index: number = 0): string { - const newKeyName = index ? name + index : name; - const indexRes = connectors.findIndex((element) => element.name === newKeyName); - return indexRes === -1 ? newKeyName : this.createConnectorName(connectors, name, ++index); - } - - private validateConnectorName(connectors: GatewayFormConnectorModel[], name: string, connectorIndex: number, index = 0): string { - for (let i = 0; i < connectors.length; i++) { - const nameEq = (index === 0) ? name : name + index; - if (i !== connectorIndex && connectors[i].name === nameEq) { - this.validateConnectorName(connectors, name, connectorIndex, ++index); - } - } - return (index === 0) ? name : name + index; - } - - changeConnectorType(connector: AbstractControl): void { - if (!connector.get('name').value) { - const typeConnector = connector.get('configType').value; - const connectors = this.gatewayConfigurationGroup.value.connectors; - connector.get('name').patchValue(this.createConnectorName(connectors, ConnectorType[typeConnector])); - } - } - - changeConnectorName(connector: AbstractControl, index: number): void { - const connectors = this.gatewayConfigurationGroup.value.connectors; - connector.get('name').patchValue(this.validateConnectorName(connectors, connector.get('name').value, index)); - } - - save(): void { - const gatewayConfiguration: GatewayFormModels = this.gatewayConfigurationGroup.value; - forkJoin([ - this.saveAttribute( - CONFIGURATION_ATTRIBUTE, - window.btoa(JSON.stringify(gatewayConfigJSON(gatewayConfiguration))), - AttributeScope.SHARED_SCOPE), - this.saveAttribute( - CONFIGURATION_DRAFT_ATTRIBUTE, - window.btoa(JSON.stringify(getDraftConnectorsJSON(gatewayConfiguration.connectors))), - AttributeScope.SERVER_SCOPE), - this.saveAttribute( - REMOTE_LOGGING_LEVEL_ATTRIBUTE, - this.gatewayConfigurationGroup.value.remoteLoggingLevel.toUpperCase(), - AttributeScope.SHARED_SCOPE) - ]).subscribe(() =>{ - this.ctx.showSuccessToast(this.successfulSaved, - 2000, 'top', 'left', this.toastTargetId); - this.gatewayConfigurationGroup.markAsPristine(); - }) - } - - private getAttributes(gatewayId: string): Array>> { - const tasks = []; - tasks.push(forkJoin([this.getAttribute(CURRENT_CONFIGURATION_ATTRIBUTE, AttributeScope.CLIENT_SCOPE, gatewayId), - this.getAttribute(CONFIGURATION_DRAFT_ATTRIBUTE, AttributeScope.SERVER_SCOPE, gatewayId)]).pipe( - tap(([currentConfig, draftConfig]) => { - this.setFormGatewaySettings(currentConfig); - this.setFormConnectorsDraft(draftConfig); - if (this.isReadOnlyForm) { - this.gatewayConfigurationGroup.disable({emitEvent: false}); - } - }) - ) - ); - tasks.push(this.getAttribute(REMOTE_LOGGING_LEVEL_ATTRIBUTE, AttributeScope.SHARED_SCOPE, gatewayId).pipe( - tap(logsLevel => this.processLoggingLevel(logsLevel)) - )); - return tasks; - } - - private getAttribute(attributeName: string, attributeScope: AttributeScope, gatewayId: string): Observable> { - return this.attributeService.getEntityAttributes(getEntityId(gatewayId), attributeScope, [attributeName]); - } - - private setFormGatewaySettings(response: Array): void { - this.connectors.clear(); - if (response.length > 0) { - const attribute = JSON.parse(window.atob(response[0].value)); - for (const attributeKey of Object.keys(attribute)) { - const keyValue = attribute[attributeKey]; - if (attributeKey === 'thingsboard') { - if (keyValue !== null && Object.keys(keyValue).length > 0) { - this.gatewayConfigurationGroup.patchValue(createFormConfig(keyValue)); - } - } else { - for (const connector of Object.keys(keyValue)) { - let name = 'No name'; - if (Object.prototype.hasOwnProperty.call(keyValue[connector], 'name')) { - name = keyValue[connector].name; - } - const newConnector: GatewayFormConnectorModel = { - enabled: true, - configType: (attributeKey as ConnectorType), - config: keyValue[connector].config, - name - }; - this.createConnector(newConnector); - } - } - } - } - } - - private setFormConnectorsDraft(response: Array): void { - if (response.length > 0) { - const attribute = JSON.parse(window.atob(response[0].value)); - for (const connectorName of Object.keys(attribute)) { - const newConnector: GatewayFormConnectorModel = { - enabled: false, - configType: (attribute[connectorName].connector as ConnectorType), - config: attribute[connectorName].config, - name: connectorName - }; - this.createConnector(newConnector); - } - } - } - - private processLoggingLevel(value: Array): void { - let logsLevel = GatewayLogLevel.debug; - if (value.length > 0 && GatewayLogLevel[value[0].value.toLowerCase()]) { - logsLevel = GatewayLogLevel[value[0].value.toLowerCase()]; - } - this.getFormField('remoteLoggingLevel').patchValue(logsLevel); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.models.ts deleted file mode 100644 index 4bdb4e966b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.models.ts +++ /dev/null @@ -1,380 +0,0 @@ -/// -/// 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 { AbstractControl, ValidationErrors } from '@angular/forms'; -import { EntityId } from '@shared/models/id/entity-id'; -import { EntityType } from '@shared/models/entity-type.models'; - -export enum SecurityType { - tls = 'tls', - accessToken = 'accessToken' -} - -export interface WidgetSetting { - widgetTitle: string; - gatewayTitle?: string; - readOnly?: boolean; - archiveFileName: string; - gatewayType: string; - successfulSave: string; - gatewayNameExists: string; -} - -export const CURRENT_CONFIGURATION_ATTRIBUTE = 'current_configuration'; -export const CONFIGURATION_DRAFT_ATTRIBUTE = 'configuration_drafts'; -export const CONFIGURATION_ATTRIBUTE = 'configuration'; -export const REMOTE_LOGGING_LEVEL_ATTRIBUTE = 'RemoteLoggingLevel'; - -export const SecurityTypeTranslationMap = new Map( - [ - [SecurityType.tls, 'gateway.security-types.tls'], - [SecurityType.accessToken, 'gateway.security-types.access-token'] - ] -); - -export enum GatewayLogLevel { - none = 'NONE', - critical = 'CRITICAL', - error = 'ERROR', - warning = 'WARNING', - info = 'INFO', - debug = 'DEBUG' -} - -export enum StorageType { - memory = 'memory', - file = 'file' -} - -export const StorageTypeTranslationMap = new Map( - [ - [StorageType.memory, 'gateway.storage-types.memory-storage'], - [StorageType.file, 'gateway.storage-types.file-storage'] - ] -); - -export enum ConnectorType { - mqtt= 'MQTT', - modbus = 'Modbus', - opcua = 'OPC-UA', - ble = 'BLE', - request = 'Request', - can = 'CAN', - bacnet = 'BACnet', - custom = 'Custom' -} - -export interface GatewayFormModels { - gateway?: string; - accessToken?: string; - securityType?: SecurityType; - host?: string; - port?: number; - remoteConfiguration?: boolean; - caCertPath?: string; - privateKeyPath?: string; - certPath?: string; - remoteLoggingLevel?: GatewayLogLevel; - remoteLoggingPathToLogs?:string; - storageType?: StorageType; - readRecordsCount?: number; - maxRecordsCount?: number; - maxFilesCount?: number; - dataFolderPath?: number; - connectors?: Array; -} - -export interface GatewayFormConnectorModel { - config: object; - name: string; - configType: ConnectorType; - enabled: boolean; -} - -export const DEFAULT_CONNECTOR: GatewayFormConnectorModel = { - config: {}, - name: '', - configType: null, - enabled: false -}; - -type Connector = { - [key in ConnectorType]?: Array; -} - -interface GatewaySetting extends Connector{ - thingsboard: GatewayMainSetting; -} - -interface ConnectorConfig { - name: string; - config: object; -} - -interface GatewayMainSetting { - thingsboard: GatewayMainThingsboardSetting; - connectors: Array, - logs: string, - storage: GatewayStorage -} - -interface GatewayMainThingsboardSetting { - host: string, - remoteConfiguration: boolean, - port: number, - security: GatewaySecurity -} - -type GatewaySecurity = SecurityToken | SecurityCertificate; - -interface SecurityToken { - accessToken: string; -} - -interface SecurityCertificate { - caCert: string; - privateKey: string; - cert: string; -} - -type GatewayStorage = GatewayStorageMemory | GatewayStorageFile; - -interface GatewayStorageMemory { - type: string; - max_records_count: number; - read_records_count: number; -} - -interface GatewayStorageFile { - type: string; - data_folder_path: number; - max_file_count: number; - max_read_records_count: number; - max_records_per_file: number; -} - -interface GatewayMainConnector { - configuration: string; - name: string; - type: ConnectorType; -} - -export function ValidateJSON(control: AbstractControl): ValidationErrors | null { - if (JSON.stringify(control.value) === JSON.stringify({})) { - return { validJSON: true }; - } - return null; -} - -const TEMPLATE_LOGS_CONFIG = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=converterHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"'; - -export function generateYAMLConfigFile(gatewaySetting: GatewayFormModels): string { - let config; - config = 'thingsboard:\n'; - config += ' host: ' + gatewaySetting.host + '\n'; - config += ' remoteConfiguration: ' + gatewaySetting.remoteConfiguration + '\n'; - config += ' port: ' + gatewaySetting.port + '\n'; - config += ' security:\n'; - if (gatewaySetting.securityType === SecurityType.accessToken) { - config += ' access-token: ' + gatewaySetting.accessToken + '\n'; - } else { - config += ' ca_cert: ' + gatewaySetting.caCertPath + '\n'; - config += ' privateKey: ' + gatewaySetting.privateKeyPath + '\n'; - config += ' cert: ' + gatewaySetting.certPath + '\n'; - } - config += 'storage:\n'; - if (gatewaySetting.storageType === StorageType.memory) { - config += ' type: memory\n'; - config += ' read_records_count: ' + gatewaySetting.readRecordsCount + '\n'; - config += ' max_records_count: ' + gatewaySetting.maxRecordsCount + '\n'; - } else { - config += ' type: file\n'; - config += ' data_folder_path: ' + gatewaySetting.dataFolderPath + '\n'; - config += ' max_file_count: ' + gatewaySetting.maxFilesCount + '\n'; - config += ' max_read_records_count: ' + gatewaySetting.readRecordsCount + '\n'; - config += ' max_records_per_file: ' + gatewaySetting.maxRecordsCount + '\n'; - } - config += 'connectors:\n'; - for(const connector of gatewaySetting.connectors){ - if (connector.enabled) { - config += ' -\n'; - config += ' name: ' + connector.name + '\n'; - config += ' type: ' + connector.configType + '\n'; - config += ' configuration: ' + generateFileName(connector.name) + '\n'; - } - } - return config; -} - -export function generateConnectorConfigFiles(fileZipAdd: object, connectors: Array): void { - for(const connector of connectors) { - if (connector.enabled) { - fileZipAdd[generateFileName(connector.name)] = JSON.stringify(connector.config); - } - } -} - -function generateFileName(fileName): string { - return fileName.replace('_', '') - .replace('-', '') - .replace(/^\s+|\s+/g, '') - .toLowerCase() + '.json'; -} - -export function generateLogConfigFile(fileZipAdd: object, logsLevel: string, logsPath: string): void { - fileZipAdd['logs.conf'] = getLogsConfig(logsLevel, logsPath); -} - -function getLogsConfig(logsLevel: string, logsPath: string): string { - return TEMPLATE_LOGS_CONFIG - .replace(/{ERROR}/g, logsLevel) - .replace(/{.\/logs\/}/g, logsPath); -} - -export function getEntityId(gatewayId: string): EntityId { - return { - id: gatewayId, - entityType: EntityType.DEVICE - } -} - -export function createFormConfig(keyValue: GatewayMainSetting): GatewayFormModels { - const formSetting: GatewayFormModels = {}; - if (Object.prototype.hasOwnProperty.call(keyValue, 'thingsboard')) { - formSetting.host = keyValue.thingsboard.host; - formSetting.port = keyValue.thingsboard.port; - formSetting.remoteConfiguration = keyValue.thingsboard.remoteConfiguration; - if (Object.prototype.hasOwnProperty.call(keyValue.thingsboard.security, SecurityType.accessToken)) { - formSetting.securityType = SecurityType.accessToken; - formSetting.accessToken = (keyValue.thingsboard.security as SecurityToken).accessToken; - } else { - formSetting.securityType = SecurityType.tls; - formSetting.caCertPath = (keyValue.thingsboard.security as SecurityCertificate).caCert; - formSetting.privateKeyPath = (keyValue.thingsboard.security as SecurityCertificate).privateKey; - formSetting.certPath = (keyValue.thingsboard.security as SecurityCertificate).cert; - } - } - - if (Object.prototype.hasOwnProperty.call(keyValue, 'storage') && Object.prototype.hasOwnProperty.call(keyValue.storage, 'type')) { - if (keyValue.storage.type === StorageType.memory) { - formSetting.storageType = StorageType.memory; - formSetting.readRecordsCount = (keyValue.storage as GatewayStorageMemory).read_records_count; - formSetting.maxRecordsCount = (keyValue.storage as GatewayStorageMemory).max_records_count; - } else if (keyValue.storage.type === StorageType.file) { - formSetting.storageType = StorageType.file; - formSetting.dataFolderPath = (keyValue.storage as GatewayStorageFile).data_folder_path; - formSetting.maxFilesCount = (keyValue.storage as GatewayStorageFile).max_file_count; - formSetting.readRecordsCount = (keyValue.storage as GatewayStorageMemory).read_records_count; - formSetting.maxRecordsCount = (keyValue.storage as GatewayStorageMemory).max_records_count; - } - } - return formSetting; -} - -export function getDraftConnectorsJSON(currentConnectors: Array) { - const draftConnectors = {}; - for(const connector of currentConnectors){ - if (!connector.enabled) { - draftConnectors[connector.name] = { - connector: connector.configType, - config: connector.config - }; - } - } - return draftConnectors; -} - -export function gatewayConfigJSON(gatewayConfiguration: GatewayFormModels): GatewaySetting { - const gatewayConfig = { - thingsboard: gatewayMainConfigJSON(gatewayConfiguration) - }; - gatewayConnectorJSON(gatewayConfig, gatewayConfiguration.connectors); - return gatewayConfig; -} - -function gatewayMainConfigJSON(gatewayConfiguration: GatewayFormModels): GatewayMainSetting { - let security: GatewaySecurity; - if (gatewayConfiguration.securityType === SecurityType.accessToken) { - security = { - accessToken: gatewayConfiguration.accessToken - } - } else { - security = { - caCert: gatewayConfiguration.caCertPath, - privateKey: gatewayConfiguration.privateKeyPath, - cert: gatewayConfiguration.certPath - } - } - const thingsboard: GatewayMainThingsboardSetting = { - host: gatewayConfiguration.host, - remoteConfiguration: gatewayConfiguration.remoteConfiguration, - port: gatewayConfiguration.port, - security - }; - - let storage: GatewayStorage; - if (gatewayConfiguration.storageType === StorageType.memory) { - storage = { - type: StorageType.memory, - read_records_count: gatewayConfiguration.readRecordsCount, - max_records_count: gatewayConfiguration.maxRecordsCount - }; - } else { - storage = { - type: StorageType.file, - data_folder_path: gatewayConfiguration.dataFolderPath, - max_file_count: gatewayConfiguration.maxFilesCount, - max_read_records_count: gatewayConfiguration.readRecordsCount, - max_records_per_file: gatewayConfiguration.maxRecordsCount - }; - } - - const connectors: Array = []; - for (const connector of gatewayConfiguration.connectors) { - if (connector.enabled) { - const connectorConfig: GatewayMainConnector = { - configuration: generateFileName(connector.name), - name: connector.name, - type: connector.configType - }; - connectors.push(connectorConfig); - } - } - - return { - thingsboard, - connectors, - storage, - logs: window.btoa(getLogsConfig(gatewayConfiguration.remoteLoggingLevel, gatewayConfiguration.remoteLoggingPathToLogs)) - } -} - -function gatewayConnectorJSON(gatewayConfiguration, currentConnectors: Array): void { - for (const connector of currentConnectors) { - if (connector.enabled) { - const typeConnector = connector.configType; - if (!Array.isArray(gatewayConfiguration[typeConnector])) { - gatewayConfiguration[typeConnector] = []; - } - - const connectorConfig: ConnectorConfig = { - name: connector.name, - config: connector.config - }; - gatewayConfiguration[typeConnector].push(connectorConfig); - } - } -} 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 deleted file mode 100644 index 098a8ce5e5..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - {{ 'widgets.gateway.created-time' | translate }} - - {{ attribute.ts | date:'yyyy-MM-dd HH:mm:ss' }} - - - - {{ 'widgets.gateway.level' | translate }} - - {{ attribute.status }} - - - - {{ 'widgets.gateway.message' | translate }} - - {{ attribute.message }} - - - - -
-{{ 'attribute.no-telemetry-text' | translate }} - - - - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.scss deleted file mode 100644 index abddccb51d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.scss +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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: flex; - flex-direction: column; - overflow-x: auto; - padding: 0; - - - .status { - border-radius: 20px; - font-weight: 500; - padding: 5px 15px; - } - - .status-debug { - color: green; - background: rgba(0, 128, 0, 0.1); - } - - .status-warning { - color: orange; - background: rgba(255, 165, 0, 0.1); - } - - .status-error { - color: red; - background: rgba(255, 0, 0, 0.1); - } - - .status-info { - color: blue; - background: rgba(0, 0, 128, 0.1); - } - - .msg-status-exception { - color: red; - } - -} - 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 deleted file mode 100644 index 5fba27f86c..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts +++ /dev/null @@ -1,200 +0,0 @@ -/// -/// 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 { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { PageLink } from '@shared/models/page/page-link'; -import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { MatSort } from '@angular/material/sort'; -import { MatTableDataSource } from '@angular/material/table'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { MatPaginator } from '@angular/material/paginator'; -import { GatewayLogData, GatewayStatus, LogLink } from './gateway-widget.models'; - -@Component({ - selector: 'tb-gateway-logs', - templateUrl: './gateway-logs.component.html', - styleUrls: ['./gateway-logs.component.scss'] -}) -export class GatewayLogsComponent implements OnInit, AfterViewInit { - - pageLink: PageLink; - - dataSource: MatTableDataSource; - - displayedColumns = ['ts', 'status', 'message']; - - @Input() - ctx: WidgetContext; - - @Input() - dialogRef: MatDialogRef; - - @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatSort) sort: MatSort; - @ViewChild(MatPaginator) paginator: MatPaginator; - - textSearchMode: boolean; - - logLinks: Array; - - activeLink: LogLink; - - gatewayLogLinks: Array = [ - { - name: 'General', - key: 'LOGS' - }, { - name: 'Service', - key: 'SERVICE_LOGS' - }, - { - name: 'Connection', - key: 'CONNECTION_LOGS' - }, { - name: 'Storage', - key: 'STORAGE_LOGS' - }, - { - key: 'EXTENSIONS_LOGS', - name: 'Extension' - }]; - - - constructor() { - const sortOrder: SortOrder = {property: 'ts', direction: Direction.DESC}; - this.pageLink = new PageLink(10, 0, null, sortOrder); - this.dataSource = new MatTableDataSource([]); - } - - ngOnInit(): void { - this.updateWidgetTitle(); - } - - ngAfterViewInit() { - this.dataSource.sort = this.sort; - this.dataSource.paginator = this.paginator; - this.ctx.defaultSubscription.onTimewindowChangeFunction = timewindow => { - this.ctx.defaultSubscription.options.timeWindowConfig = timewindow; - this.ctx.defaultSubscription.updateDataSubscriptions(); - return timewindow; - }; - if (this.ctx.settings.isConnectorLog && this.ctx.settings.connectorLogState) { - const connector = this.ctx.stateController.getStateParams()[this.ctx.settings.connectorLogState]; - this.logLinks = [{ - key: `${connector.key}_LOGS`, - name: 'Connector', - filterFn: (attrData) => !attrData.message.includes(`_converter.py`) - }, { - key: `${connector.key}_LOGS`, - name: 'Converter', - filterFn: (attrData) => attrData.message.includes(`_converter.py`) - }]; - } else { - this.logLinks = this.gatewayLogLinks; - } - this.activeLink = this.logLinks[0]; - this.changeSubscription(); - } - - private updateWidgetTitle(): void { - if (this.ctx.settings.isConnectorLog && this.ctx.settings.connectorLogState) { - const widgetTitle = this.ctx.widgetConfig.title; - const titlePlaceholder = '${connectorName}'; - if (widgetTitle.includes(titlePlaceholder)) { - const connector = this.ctx.stateController.getStateParams()[this.ctx.settings.connectorLogState]; - this.ctx.widgetTitle = widgetTitle.replace(titlePlaceholder, connector.key); - } - } - } - - - private updateData() { - if (this.ctx.defaultSubscription.data.length && this.ctx.defaultSubscription.data[0]) { - let attrData = this.ctx.defaultSubscription.data[0].data.map(data => { - const result = { - ts: data[0], - key: this.activeLink.key, - message: data[1], - status: 'INVALID LOG FORMAT' as GatewayStatus - }; - - try { - result.message = /\[(.*)/.exec(data[1])[0]; - } catch (e) { - result.message = data[1]; - } - - try { - result.status = data[1].match(/\|(\w+)\|/)[1]; - } catch (e) { - result.status = 'INVALID LOG FORMAT' as GatewayStatus; - } - - return result; - }); - if (this.activeLink.filterFn) { - attrData = attrData.filter(data => this.activeLink.filterFn(data)); - } - this.dataSource.data = attrData; - } - } - - onTabChanged(link: LogLink) { - this.activeLink = link; - this.changeSubscription(); - } - - statusClass(status: GatewayStatus): string { - switch (status) { - case GatewayStatus.DEBUG: - return 'status status-debug'; - case GatewayStatus.WARNING: - return 'status status-warning'; - case GatewayStatus.ERROR: - case GatewayStatus.EXCEPTION: - return 'status status-error'; - default: - return 'status status-info'; - } - } - - statusClassMsg(status?: GatewayStatus): string { - if (status === GatewayStatus.EXCEPTION) { - return 'msg-status-exception'; - } - } - - 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 = [{ - name: this.activeLink.key, - type: DataKeyType.timeseries, - settings: {} - }]; - this.ctx.defaultSubscription.unsubscribe(); - this.ctx.defaultSubscription.updateDataSubscriptions(); - this.ctx.defaultSubscription.callbacks.onDataUpdated = () => { - this.updateData(); - }; - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.html deleted file mode 100644 index 0d5d96e45b..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.html +++ /dev/null @@ -1,52 +0,0 @@ - - - warning -

gateway.configuration-delete-dialog-header

- - -
-
- - - gateway.configuration-delete-dialog-input - - - {{ 'gateway.configuration-delete-dialog-input-required' | translate }} - - -
-
- - -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.ts deleted file mode 100644 index b4725016ba..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-remote-configuration-dialog.ts +++ /dev/null @@ -1,57 +0,0 @@ -/// -/// 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 { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Router } from '@angular/router'; -import { FormBuilder, FormControl } from '@angular/forms'; - -export interface GatewayRemoteConfigurationDialogData { - gatewayName: string; -} - -@Component({ - selector: 'gateway-remote-configuration-dialog', - templateUrl: './gateway-remote-configuration-dialog.html' -}) - -export class GatewayRemoteConfigurationDialogComponent extends - DialogComponent{ - - gatewayName: string; - gatewayControl: FormControl; - - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: GatewayRemoteConfigurationDialogData, - public dialogRef: MatDialogRef, - private fb: FormBuilder) { - super(store, router, dialogRef); - this.gatewayName = this.data.gatewayName; - this.gatewayControl = this.fb.control(''); - } - - close(): void { - this.dialogRef.close(); - } - - turnOff(): void { - this.dialogRef.close(true); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html deleted file mode 100644 index d8b028361d..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html +++ /dev/null @@ -1,54 +0,0 @@ - - -

gateway.rpc.save-template

- - -
-
- - gateway.rpc.template-name - - - {{ 'gateway.rpc.template-name-required' | translate }} - - -
- {{ 'gateway.rpc.template-name-duplicate' | translate }} -
-
-
- - -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts deleted file mode 100644 index 5c70e1f233..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts +++ /dev/null @@ -1,63 +0,0 @@ -/// -/// 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 { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Router } from '@angular/router'; -import { FormBuilder, FormControl, UntypedFormControl, Validators } from '@angular/forms'; -import { RPCTemplate, SaveRPCTemplateData } from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Component({ - selector: 'gateway-service-rpc-connector-template-dialog', - templateUrl: './gateway-service-rpc-connector-template-dialog.html' -}) - -export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogComponent { - - config: { - [key: string]: any; - }; - templates: Array; - templateNameCtrl: FormControl; - - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: SaveRPCTemplateData, - public dialogRef: MatDialogRef, - public fb: FormBuilder) { - super(store, router, dialogRef); - this.config = this.data.config; - this.templates = this.data.templates; - this.templateNameCtrl = this.fb.control('', [Validators.required]); - } - - validateDuplicateName(c: UntypedFormControl) { - const name = c.value.trim(); - return !!this.templates.find((template) => template.name === name); - }; - - close(): void { - this.dialogRef.close(); - } - - save(): void { - this.templateNameCtrl.setValue(this.templateNameCtrl.value.trim()); - if (this.templateNameCtrl.valid) this.dialogRef.close(this.templateNameCtrl.value); - } -} 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 deleted file mode 100644 index 0d01a1c1d7..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ /dev/null @@ -1,67 +0,0 @@ - -
{{ 'gateway.rpc.templates-title' | translate }}
- - - - {{template.name}} - - - - - - - - - - -
-
- {{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}} -
-
- {{ config.value | getRpcTemplateArrayView }} -
- -
- -
- {{ config.value }} - {{ SNMPMethodsTranslations.get(config.value) | translate }} - - - - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss deleted file mode 100644 index bf037a7249..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 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: flex; - flex-direction: column; - padding: 0; - - .title { - font-weight: 500; - } - - .template-key { - color: rgba(0, 0, 0, 0.38); - height: 32px; - line-height: 32px; - } - - .boolean-true, .boolean-false { - border-radius: 3px; - height: 32px; - line-height: 32px; - padding: 0 12px; - width: fit-content; - font-size: 14px; - text-transform: capitalize; - } - - .boolean-false { - color: #d12730; - background-color: rgba(209, 39, 48, 0.08); - } - - .boolean-true { - color: #198038; - background-color: rgba(25, 128, 56, 0.08); - } - - mat-expansion-panel { - margin-top: 10px; - overflow: visible; - } - - .mat-expansion-panel-header-description { - flex-direction: row-reverse; - align-items: center; - margin-right: 0; - flex: 0; - - & > mat-icon { - margin-left: 15px; - color: rgba(0, 0, 0, 0.38); - } - } - - .mat-expansion-panel-header { - padding: 0 0 0 12px; - - &.mat-expansion-panel-header.mat-expanded { - height: 48px; - } - - .mat-content.mat-content-hide-toggle { - margin-right: 0; - } - } - - .rpc-params-row { - overflow: hidden; - white-space: nowrap; - - & :not(:first-child) { - white-space: pre; - overflow: hidden; - text-overflow: ellipsis; - } - } - - .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; - } - - .array-value { - margin-left: 10px; - } -} - - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts deleted file mode 100644 index 9c2e551a22..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -/// -/// 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 { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { - ConnectorType, - 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'; -import { AttributeService } from '@core/http/attribute.service'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { isLiteralObject } from '@app/core/utils'; - -@Component({ - selector: 'tb-gateway-service-rpc-connector-templates', - templateUrl: './gateway-service-rpc-connector-templates.component.html', - styleUrls: ['./gateway-service-rpc-connector-templates.component.scss'] -}) -export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { - - @Input() - connectorType: ConnectorType; - - @Input() - ctx: WidgetContext; - - @Output() - saveTemplate: EventEmitter = new EventEmitter(); - - @Output() - useTemplate: EventEmitter = new EventEmitter(); - - @Input() - rpcTemplates: Array; - - public readonly originalOrder = (): number => 0; - public readonly isObject = (value: unknown) => isLiteralObject(value); - public readonly isArray = (value: unknown) => Array.isArray(value); - public readonly SNMPMethodsTranslations = SNMPMethodsTranslations; - - constructor(private attributeService: AttributeService) { - } - - ngOnInit() { - } - - public applyTemplate($event: Event, template: RPCTemplate): void { - $event.stopPropagation(); - this.useTemplate.emit(template); - } - - public deleteTemplate($event: Event, template: RPCTemplate): void { - $event.stopPropagation(); - const index = this.rpcTemplates.findIndex(data => { - return data.name == template.name; - }) - this.rpcTemplates.splice(index, 1); - const key = `${this.connectorType}_template`; - this.attributeService.saveEntityAttributes( - { - id: this.ctx.defaultSubscription.targetDeviceId, - entityType: EntityType.DEVICE - } - , AttributeScope.SERVER_SCOPE, [{ - key, - value: this.rpcTemplates - }]).subscribe(() => { - }) - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html deleted file mode 100644 index fde4ab28ac..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ /dev/null @@ -1,413 +0,0 @@ - -
-
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
- - - - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.requestType' | translate }} - - - {{bACnetRequestTypesTranslates.get(type) | translate}} - - - - - {{ 'gateway.rpc.requestTimeout' | translate }} - - -
- - {{ 'gateway.rpc.objectType' | translate }} - - - {{bACnetObjectTypesTranslates.get(type) | translate}} - - - - - {{ 'gateway.rpc.identifier' | translate }} - - -
- - {{ 'gateway.rpc.propertyId' | translate }} - - -
- - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.characteristicUUID' | translate }} - - - - {{ 'gateway.rpc.methodProcessing' | translate }} - - - {{bLEMethodsTranslates.get(type) | translate}} - - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.nodeID' | translate }} - - - - {{ 'gateway.rpc.isExtendedID' | translate }} - - - {{ 'gateway.rpc.isFD' | translate }} - - - {{ 'gateway.rpc.bitrateSwitch' | translate }} - -
- - {{ 'gateway.rpc.dataLength' | translate }} - - - - {{ 'gateway.rpc.dataByteorder' | translate }} - - - {{ order | translate }} - - - -
-
- - {{ 'gateway.rpc.dataBefore' | translate }} - - - - {{ 'gateway.rpc.dataAfter' | translate }} - - -
- - {{ 'gateway.rpc.dataInHEX' | translate }} - - - - {{ 'gateway.rpc.dataExpression' | translate }} - - -
- - - {{ 'gateway.rpc.methodFilter' | translate }} - - - - {{ 'gateway.rpc.valueExpression' | translate }} - - - - - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.valueExpression' | translate }} - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.methodProcessing' | translate }} - - - {{ SocketMethodProcessingsTranslates.get(method) | translate }} - - - - - {{ 'gateway.rpc.encoding' | translate }} - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - - - {{ 'gateway.rpc.methodRPC' | translate }} - - - - {{ 'gateway.rpc.valueExpression' | translate }} - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - - - {{ 'gateway.rpc.requestFilter' | translate }} - - - - {{ 'gateway.rpc.method' | translate }} - - - {{ SNMPMethodsTranslations.get(method) | translate }} - - - - - {{ 'gateway.rpc.withResponse' | translate }} - -
- {{ 'gateway.rpc.oids' | translate }}* -
- - - - delete - -
- -
-
- - - {{ 'gateway.rpc.methodFilter' | translate }} - - -
- - {{ 'gateway.rpc.httpMethod' | translate }} - - - {{ method }} - - - - - {{ 'gateway.rpc.requestUrlExpression' | translate }} - - -
-
- - {{ 'gateway.rpc.responseTimeout' | translate }} - - - - {{ 'gateway.rpc.timeout' | translate }} - - - - {{ 'gateway.rpc.tries' | translate }} - - -
- - {{ 'gateway.rpc.valueExpression' | translate }} - - -
- {{ 'gateway.rpc.httpHeaders' | translate }} -
-
- {{ 'gateway.rpc.header-name' | translate }} - {{ 'gateway.rpc.value' | translate }} - -
- -
- - - - - - - - delete - - -
-
- -
- -
- - - {{ 'gateway.rpc.methodFilter' | translate }} - - -
- - {{ 'gateway.rpc.httpMethod' | translate }} - - - {{ method }} - - - - - {{ 'gateway.rpc.requestUrlExpression' | translate }} - - -
-
- - {{ 'gateway.rpc.responseTimeout' | translate }} - - - - {{ 'gateway.rpc.timeout' | translate }} - - - - {{ 'gateway.rpc.tries' | translate }} - - -
- - {{ 'gateway.rpc.requestValueExpression' | translate }} - - - - {{ 'gateway.rpc.responseValueExpression' | translate }} - - -
- {{ 'gateway.rpc.httpHeaders' | translate }} -
-
- {{ 'gateway.rpc.header-name' | translate }} - {{ 'gateway.rpc.value' | translate }} - -
- -
- - - - - - - - delete - - -
-
- -
-
- - - {{ 'gateway.statistics.command' | translate }} - - - - {{ 'widget-config.datasource-parameters' | translate }} - - edit - - - {{ 'gateway.rpc.json-value-invalid' | translate }} - - - -
-
-
- - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss deleted file mode 100644 index a0af50aee8..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 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: flex; - flex-direction: column; - padding: 0; - - .title { - font-weight: 500; - } - - .command-form { - flex-wrap: nowrap; - - & > button { - margin-top: 10px; - } - } - - .mat-mdc-slide-toggle.margin { - margin-bottom: 10px; - margin-left: 10px; - } - - .fields{ - .fields-label { - font-weight: 500; - } - } - - .border { - padding: 16px; - margin-bottom: 10px; - box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); - border: solid 1px #e0e0e0; - border-radius: 4px; - - .title { - color: rgba(0, 0, 0, .54); - } - - .mat-icon { - color: rgba(0, 0, 0, .38); - } - - .mat-divider { - margin-left: -16px; - margin-right: -16px; - margin-bottom: 16px; - } - } -} 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 deleted file mode 100644 index f77e877aea..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ /dev/null @@ -1,345 +0,0 @@ -/// -/// 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 { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { - ControlValueAccessor, - FormArray, - FormBuilder, - FormControl, - FormGroup, - NG_VALUE_ACCESSOR, - Validators -} from '@angular/forms'; -import { - BACnetObjectTypes, - BACnetObjectTypesTranslates, - BACnetRequestTypes, - BACnetRequestTypesTranslates, - BLEMethods, - BLEMethodsTranslates, - CANByteOrders, - ConnectorType, - GatewayConnectorDefaultTypesTranslatesMap, - HTTPMethods, - noLeadTrailSpacesRegex, - RPCCommand, - RPCTemplateConfig, - SNMPMethods, - SNMPMethodsTranslations, - SocketEncodings, - SocketMethodProcessings, - SocketMethodProcessingsTranslates -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { MatDialog } from '@angular/material/dialog'; -import { - JsonObjectEditDialogComponent, - JsonObjectEditDialogData -} from '@shared/components/dialog/json-object-edit-dialog.component'; -import { jsonRequired } from '@shared/components/json-object-edit.component'; -import { deepClone } from '@core/utils'; -import { Subject } from "rxjs"; - -@Component({ - selector: 'tb-gateway-service-rpc-connector', - templateUrl: './gateway-service-rpc-connector.component.html', - styleUrls: ['./gateway-service-rpc-connector.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => GatewayServiceRPCConnectorComponent), - multi: true - } - ] -}) -export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, ControlValueAccessor { - - @Input() - connectorType: ConnectorType; - - @Output() - sendCommand: EventEmitter = new EventEmitter(); - - @Output() - saveTemplate: EventEmitter = new EventEmitter(); - - commandForm: FormGroup; - ConnectorType = ConnectorType; - bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[]; - bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[]; - bLEMethods = Object.values(BLEMethods) as BLEMethods[]; - cANByteOrders = Object.values(CANByteOrders) as CANByteOrders[]; - socketMethodProcessings = Object.values(SocketMethodProcessings) as SocketMethodProcessings[]; - socketEncodings = Object.values(SocketEncodings) as SocketEncodings[]; - sNMPMethods = Object.values(SNMPMethods) as SNMPMethods[]; - hTTPMethods = Object.values(HTTPMethods) as HTTPMethods[]; - - bACnetRequestTypesTranslates = BACnetRequestTypesTranslates; - bACnetObjectTypesTranslates = BACnetObjectTypesTranslates; - bLEMethodsTranslates = BLEMethodsTranslates; - SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates; - SNMPMethodsTranslations = SNMPMethodsTranslations; - gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap; - - urlPattern = /^[-a-zA-Zd_$:{}?~+=\/.0-9-]*$/; - numbersOnlyPattern = /^[0-9]*$/; - hexOnlyPattern = /^[0-9A-Fa-f ]+$/; - - private propagateChange = (v: any) => { - } - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder, - private dialog: MatDialog,) { - } - - ngOnInit() { - this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); - this.commandForm.valueChanges.subscribe(value => { - const httpHeaders = {}; - switch (this.connectorType) { - case ConnectorType.REST: - value.httpHeaders.forEach(data => { - httpHeaders[data.headerName] = data.value; - }) - value.httpHeaders = httpHeaders; - break; - case ConnectorType.REQUEST: - value.httpHeaders.forEach(data => { - httpHeaders[data.headerName] = data.value; - }) - value.httpHeaders = httpHeaders; - break; - } - if (this.commandForm.valid) { - this.propagateChange({...this.commandForm.value, ...value}); - } - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - connectorParamsFormGroupByType(type: ConnectorType): FormGroup { - let formGroup: FormGroup; - - switch (type) { - case ConnectorType.BACNET: - formGroup = this.fb.group({ - method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestType: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], - objectType: [null, []], - identifier: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], - propertyId: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }) - break; - case ConnectorType.BLE: - formGroup = this.fb.group({ - methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - characteristicUUID: ["00002A00-0000-1000-8000-00805F9B34FB", [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - methodProcessing: [null, [Validators.required]], - withResponse: [false, []] - }) - break; - case ConnectorType.CAN: - formGroup = this.fb.group({ - method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]], - isExtendedID: [false, []], - isFD: [false, []], - bitrateSwitch: [false, []], - dataLength: [null, [Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], - dataByteorder: [null, []], - dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], - dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], - dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], - dataExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]] - }) - break; - case ConnectorType.FTP: - formGroup = this.fb.group({ - methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }) - break; - case ConnectorType.OCPP: - formGroup = this.fb.group({ - methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - withResponse: [false, []] - }) - break; - case ConnectorType.SOCKET: - formGroup = this.fb.group({ - methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - methodProcessing: [null, [Validators.required]], - encoding: [SocketEncodings.UTF_8, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - withResponse: [false, []] - }) - break; - case ConnectorType.XMPP: - formGroup = this.fb.group({ - methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - withResponse: [false, []] - }) - break; - case ConnectorType.SNMP: - formGroup = this.fb.group({ - requestFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - method: [null, [Validators.required]], - withResponse: [false, []], - oid: this.fb.array([], [Validators.required]) - }) - break; - case ConnectorType.REST: - formGroup = this.fb.group({ - methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - httpMethod: [null, [Validators.required]], - requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], - responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], - timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], - tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - httpHeaders: this.fb.array([]), - security: [{}, [Validators.required]] - }) - break; - case ConnectorType.REQUEST: - formGroup = this.fb.group({ - methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - httpMethod: [null, [Validators.required]], - requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], - responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], - timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], - tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], - requestValueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - responseValueExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], - httpHeaders: this.fb.array([]), - }) - break; - default: - formGroup = this.fb.group({ - command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - params: [{}, [jsonRequired]], - }) - } - return formGroup; - } - - addSNMPoid(value: string = null) { - const oidsFA = this.commandForm.get('oid') as FormArray; - if (oidsFA) { - oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false}); - } - } - - removeSNMPoid(index: number) { - const oidsFA = this.commandForm.get('oid') as FormArray; - oidsFA.removeAt(index); - } - - addHTTPHeader(value: { headerName: string, value: string } = {headerName: null, value: null}) { - const headerFA = this.commandForm.get('httpHeaders') as FormArray; - const formGroup = this.fb.group({ - headerName: [value.headerName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] - }) - if (headerFA) { - headerFA.push(formGroup, {emitEvent: false}); - } - } - - removeHTTPHeader(index: number) { - const oidsFA = this.commandForm.get('httpHeaders') as FormArray; - oidsFA.removeAt(index); - } - - getFormArrayControls(path: string) { - return (this.commandForm.get(path) as FormArray).controls as FormControl[]; - } - - openEditJSONDialog($event: Event) { - if ($event) { - $event.stopPropagation(); - } - this.dialog.open(JsonObjectEditDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonValue: this.commandForm.get('params').value, - required: true - } - }).afterClosed().subscribe( - (res) => { - if (res) { - this.commandForm.get('params').setValue(res); - } - } - ); - } - - save() { - this.saveTemplate.emit(); - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void { - } - - clearFromArrayByName(name: string) { - const formArray = this.commandForm.get(name) as FormArray; - while (formArray.length !== 0) { - formArray.removeAt(0) - } - } - - writeValue(value: RPCTemplateConfig): void { - if (typeof value == "object") { - value = deepClone(value); - switch (this.connectorType) { - case ConnectorType.SNMP: - this.clearFromArrayByName("oid"); - value.oid.forEach(value => { - this.addSNMPoid(value) - }) - delete value.oid; - break; - case ConnectorType.REQUEST: - this.clearFromArrayByName("httpHeaders"); - value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { - this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) - }) - delete value.httpHeaders; - break; - case ConnectorType.REST: - this.clearFromArrayByName("httpHeaders"); - value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { - this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) - }) - delete value.httpHeaders; - break; - } - this.commandForm.patchValue(value, {onlySelf: false}); - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html deleted file mode 100644 index 41a655d93e..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ /dev/null @@ -1,88 +0,0 @@ - -
-
- - - {{ 'gateway.statistics.command' | translate }} - - - {{ command }} - - - - - {{ 'gateway.statistics.timeout-ms' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - - - - - -
-
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
- - - - - -
- - -
-
-
-
-
-
- {{ 'gateway.rpc-command-result' | translate }} -
- schedule - {{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }} -
-
- -
-
- - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss deleted file mode 100644 index b8424dc068..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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%; - overflow: auto; - display: flex; - flex-direction: row; - padding: 0 5px; - - & > * { - height: 100%; - overflow: auto; - } - - & > tb-gateway-service-rpc-connector-templates:last-child { - margin-left: 10px; - } - - - .command-form { - flex-wrap: nowrap; - padding: 0 5px 5px; - - & > button { - margin-top: 10px; - } - } - - .rpc-parameters { - width: 100%; - } - - .result-block { - padding: 0 5px; - display: flex; - flex-direction: column; - flex: 1; - - & > span { - font-weight: 600; - position: relative; - font-size: 14px; - margin-bottom: 10px; - - - .result-time { - font-weight: 400; - font-size: 14px; - line-height: 32px; - position: absolute; - left: 0; - top: 25px; - z-index: 5; - color: rgba(0, 0, 0, 0.54); - - span { - padding-left: 10px; - } - } - } - - tb-json-content { - flex: 1; - } - } - - .border { - padding: 16px; - box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); - border: solid 1px #e0e0e0; - border-radius: 4px; - } -} 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 deleted file mode 100644 index 43050ca5f7..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ /dev/null @@ -1,230 +0,0 @@ -/// -/// 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 { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { ContentType } from '@shared/models/constants'; -import { jsonRequired } from '@shared/components/json-object-edit.component'; -import { - ConnectorType, - GatewayConnectorDefaultTypesTranslatesMap, - RPCCommand, - RPCTemplate, - RPCTemplateConfig, - SaveRPCTemplateData -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { - GatewayServiceRPCConnectorTemplateDialogComponent -} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; -import { AttributeService } from '@core/http/attribute.service'; -import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { EntityType } from '@shared/models/entity-type.models'; -import { DatasourceType, widgetType } from '@shared/models/widget.models'; -import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { UtilsService } from '@core/services/utils.service'; - -@Component({ - selector: 'tb-gateway-service-rpc', - templateUrl: './gateway-service-rpc.component.html', - styleUrls: ['./gateway-service-rpc.component.scss'] -}) -export class GatewayServiceRPCComponent implements OnInit { - - @Input() - ctx: WidgetContext; - - contentTypes = ContentType; - - resultTime: number | null; - - @Input() - dialogRef: MatDialogRef; - - commandForm: FormGroup; - - isConnector: boolean; - - RPCCommands: Array = [ - 'Ping', - 'Stats', - 'Devices', - 'Update', - 'Version', - 'Restart', - 'Reboot' - ]; - - public connectorType: ConnectorType; - public templates: Array = []; - - readonly ConnectorType = ConnectorType; - readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap; - readonly typesWithUpdatedParams = new Set([ - ConnectorType.MQTT, - ConnectorType.OPCUA, - ConnectorType.MODBUS, - ]); - - private subscription: IWidgetSubscription; - private subscriptionOptions: WidgetSubscriptionOptions = { - callbacks: { - onDataUpdated: () => this.ctx.ngZone.run(() => { - this.updateTemplates() - }), - onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { - this.onDataUpdateError(e); - }), - dataLoading: () => { - } - } - }; - - constructor(private fb: FormBuilder, - private dialog: MatDialog, - private utils: UtilsService, - private cd: ChangeDetectorRef, - private attributeService: AttributeService) { - this.commandForm = this.fb.group({ - command: [null, [Validators.required]], - time: [60, [Validators.required, Validators.min(1)]], - params: ['{}', [jsonRequired]], - result: [null] - }); - } - - ngOnInit() { - this.isConnector = this.ctx.settings.isConnector; - if (!this.isConnector) { - this.commandForm.get('command').setValue(this.RPCCommands[0]); - } else { - this.connectorType = this.ctx.stateController.getStateParams().connector_rpc.value.type; - const subscriptionInfo = [{ - type: DatasourceType.entity, - entityType: EntityType.DEVICE, - entityId: this.ctx.defaultSubscription.targetDeviceId, - entityName: 'Connector', - attributes: [{name: `${this.connectorType}_template`}] - }]; - this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo, - this.subscriptionOptions, false, true).subscribe(subscription => { - this.subscription = subscription; - }) - } - } - - sendCommand(value?: RPCCommand) { - this.resultTime = null; - const formValues = value || this.commandForm.value; - const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_'; - const command = !this.isConnector ? formValues.command.toLowerCase() : this.getCommandFromParamsByType(formValues.params); - const params = formValues.params; - this.ctx.controlApi.sendTwoWayCommand(commandPrefix + command, params, formValues.time).subscribe({ - next: resp => { - this.resultTime = new Date().getTime(); - this.commandForm.get('result').setValue(JSON.stringify(resp)) - }, - error: error => { - this.resultTime = new Date().getTime(); - console.error(error); - this.commandForm.get('result').setValue(JSON.stringify(error.error)); - } - }); - } - - getCommandFromParamsByType(params: RPCTemplateConfig) { - switch (this.connectorType) { - case ConnectorType.MQTT: - case ConnectorType.FTP: - case ConnectorType.SNMP: - case ConnectorType.REST: - case ConnectorType.REQUEST: - return params.methodFilter; - case ConnectorType.MODBUS: - return params.tag; - case ConnectorType.BACNET: - case ConnectorType.CAN: - case ConnectorType.OPCUA: - return params.method; - case ConnectorType.BLE: - case ConnectorType.OCPP: - case ConnectorType.SOCKET: - case ConnectorType.XMPP: - return params.methodRPC; - default: - return params.command; - } - } - - saveTemplate() { - this.dialog.open - (GatewayServiceRPCConnectorTemplateDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: {config: this.commandForm.value.params, templates: this.templates} - }).afterClosed().subscribe( - (res) => { - if (res) { - const templateAttribute: RPCTemplate = { - name: res, - config: this.commandForm.value.params - } - const templatesArray = this.templates; - const existingIndex = templatesArray.findIndex(template => { - return template.name == templateAttribute.name; - }) - if (existingIndex > -1) { - templatesArray.splice(existingIndex, 1) - } - templatesArray.push(templateAttribute) - const key = `${this.connectorType}_template`; - this.attributeService.saveEntityAttributes( - { - id: this.ctx.defaultSubscription.targetDeviceId, - entityType: EntityType.DEVICE - } - , AttributeScope.SERVER_SCOPE, [{ - key, - value: templatesArray - }]).subscribe(() => { - this.cd.detectChanges(); - }) - } - } - ); - } - - useTemplate($event) { - this.commandForm.get('params').patchValue($event.config); - } - - private updateTemplates() { - this.templates = this.subscription.data[0].data[0][1].length ? - JSON.parse(this.subscription.data[0].data[0][1]) : []; - this.cd.detectChanges(); - } - - private onDataUpdateError(e: any) { - const exceptionData = this.utils.parseException(e); - let errorText = exceptionData.name; - if (exceptionData.message) { - errorText += ': ' + exceptionData.message; - } - console.error(errorText); - } - -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.html deleted file mode 100644 index 5dbc0aee00..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.html +++ /dev/null @@ -1,86 +0,0 @@ - -
- - - {{ 'gateway.statistics.statistic' | translate }} - - - {{ key }} - - - {{ command.attributeOnGateway }} - - - - - {{ 'gateway.statistics.statistic-commands-empty' | translate }} - -
- -
- - {{ 'gateway.statistics.command' | translate }} - - -
-
-
- - - {{ 'widgets.gateway.created-time' | translate }} - - - {{ row[0]| date:'yyyy-MM-dd HH:mm:ss' }} - - - - {{ 'widgets.gateway.message' | translate }} - - - {{ row[1] }} - - - - -
- {{ 'attribute.no-telemetry-text' | translate }} -
-
- -
-
-
-
-
-
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss deleted file mode 100644 index 9f719807ca..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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%; - padding: 0; - - - .statistics-container { - height: 100%; - overflow-y: auto; - - mat-card { - width: 40%; - height: 100%; - margin-right: 35px; - padding: 15px; - gap: 22px; - } - - @media only screen and (max-width: 750px) { - mat-card { - width: 100%; - } - } - - .chart-box, .chart-container { - height: 100%; - flex-grow: 1; - } - - .chart-box { - overflow: auto; - } - - & > * { - height: 100%; - } - } - - .legend { - flex-wrap: wrap; - width: 100%; - padding-top: 8px; - padding-bottom: 4px; - margin-top: 15px; - - .legend-keys { - .legend-label { - padding: 2px 20px 2px 10px; - white-space: nowrap; - - &.hidden-label { - text-decoration: line-through; - opacity: .6; - } - - &:focus { - outline: none; - } - } - - .legend-line { - display: inline-block; - width: 15px; - height: 3px; - text-align: left; - vertical-align: middle; - outline: none; - } - } - } -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts deleted file mode 100644 index 2537f415da..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts +++ /dev/null @@ -1,291 +0,0 @@ -/// -/// 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 { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { AttributeService } from '@core/http/attribute.service'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { TbFlot } from '@home/components/widget/lib/flot-widget'; -import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { UtilsService } from '@core/services/utils.service'; -import { DatasourceType, LegendConfig, LegendData, LegendPosition, widgetType } from '@shared/models/widget.models'; -import { EntityType } from '@shared/models/entity-type.models'; -import { EntityId } from '@shared/models/id/entity-id'; -import { BaseData } from '@shared/models/base-data'; -import { PageLink } from '@shared/models/page/page-link'; -import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { MatTableDataSource } from '@angular/material/table'; -import { MatSort } from '@angular/material/sort'; -import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { deepClone } from '@core/utils'; - -@Component({ - selector: 'tb-gateway-statistics', - templateUrl: './gateway-statistics.component.html', - styleUrls: ['./gateway-statistics.component.scss'] -}) -export class GatewayStatisticsComponent implements AfterViewInit { - - @ViewChild(MatSort) sort: MatSort; - @ViewChild('statisticChart') statisticChart: ElementRef; - - @Input() - ctx: WidgetContext; - - @Input() - public general: boolean; - - public isNumericData = false; - public dataTypeDefined: boolean = false; - public chartInited: boolean; - private flot: TbFlot; - private flotCtx: WidgetContext; - public statisticForm: FormGroup; - public statisticsKeys = []; - public commands = []; - public commandObj: any; - public dataSource: MatTableDataSource; - public pageLink: PageLink; - private resize$: ResizeObserver; - private subscription: IWidgetSubscription; - private subscriptionInfo: SubscriptionInfo []; - public legendData: LegendData; - public displayedColumns: Array; - private subscriptionOptions: WidgetSubscriptionOptions = { - callbacks: { - onDataUpdated: () => this.ctx.ngZone.run(() => { - this.onDataUpdated(); - }), - onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { - this.onDataUpdateError(e); - }) - }, - useDashboardTimewindow: false, - legendConfig: { - position: LegendPosition.bottom - } as LegendConfig - }; - - - constructor(private fb: FormBuilder, - private attributeService: AttributeService, - private utils: UtilsService) { - const sortOrder: SortOrder = {property: '0', direction: Direction.DESC}; - this.pageLink = new PageLink(Number.POSITIVE_INFINITY, 0, null, sortOrder); - this.displayedColumns = ['0', '1']; - this.dataSource = new MatTableDataSource([]); - this.statisticForm = this.fb.group({ - statisticKey: [null, []] - }); - - this.statisticForm.get('statisticKey').valueChanges.subscribe(value => { - this.commandObj = null; - if (this.commands.length) { - this.commandObj = this.commands.find(command => command.attributeOnGateway === value); - } - if (this.subscriptionInfo) { - this.createChartsSubscription(this.ctx.defaultSubscription.datasources[0].entity, value); - } - }); - } - - - ngAfterViewInit() { - this.dataSource.sort = this.sort; - this.sort.sortChange.subscribe(() => this.sortData()); - this.init(); - if (this.ctx.defaultSubscription.datasources.length) { - const gateway = this.ctx.defaultSubscription.datasources[0].entity; - if (gateway.id.id === NULL_UUID) { - return; - } - if (!this.general) { - this.attributeService.getEntityAttributes(gateway.id, AttributeScope.SHARED_SCOPE, ['general_configuration']) - .subscribe((resp: AttributeData[]) => { - if (resp && resp.length) { - this.commands = resp[0].value.statistics.commands; - if (!this.statisticForm.get('statisticKey').value && this.commands && this.commands.length) { - this.statisticForm.get('statisticKey').setValue(this.commands[0].attributeOnGateway); - this.createChartsSubscription(gateway, this.commands[0].attributeOnGateway); - } - } - }); - } else { - this.attributeService.getEntityTimeseriesLatest(gateway.id).subscribe( - data => { - const connectorsTs = Object.keys(data) - .filter(el => el.includes('ConnectorEventsProduced') || el.includes('ConnectorEventsSent')); - this.createGeneralChartsSubscription(gateway, connectorsTs); - }); - } - } - } - - public navigateToStatistics() { - const params = deepClone(this.ctx.stateController.getStateParams()); - this.ctx.stateController.openState('configuration', params); - } - - public sortData() { - this.dataSource.sortData(this.dataSource.data, this.sort); - } - - public onLegendKeyHiddenChange(index: number) { - this.legendData.keys[index].dataKey.hidden = !this.legendData.keys[index].dataKey.hidden; - this.subscription.updateDataVisibility(index); - } - - private createChartsSubscription(gateway: BaseData, attr: string) { - const subscriptionInfo = [{ - type: DatasourceType.entity, - entityType: EntityType.DEVICE, - entityId: gateway.id.id, - entityName: gateway.name, - timeseries: [] - }]; - - subscriptionInfo[0].timeseries = [{name: attr, label: attr}]; - this.subscriptionInfo = subscriptionInfo; - this.changeSubscription(subscriptionInfo); - this.ctx.defaultSubscription.unsubscribe(); - } - - private createGeneralChartsSubscription(gateway: BaseData, attrData: string[]) { - const subscriptionInfo = [{ - type: DatasourceType.entity, - entityType: EntityType.DEVICE, - entityId: gateway.id.id, - entityName: gateway.name, - timeseries: [] - }]; - subscriptionInfo[0].timeseries = []; - if (attrData?.length) { - attrData.forEach(attr => { - subscriptionInfo[0].timeseries.push({name: attr, label: attr}); - }); - } - this.ctx.defaultSubscription.datasources[0].dataKeys.forEach(dataKey => { - subscriptionInfo[0].timeseries.push({name: dataKey.name, label: dataKey.label}); - }); - - this.changeSubscription(subscriptionInfo); - this.ctx.defaultSubscription.unsubscribe(); - } - - private init = () => { - this.flotCtx = { - $scope: this.ctx.$scope, - $injector: this.ctx.$injector, - utils: this.ctx.utils, - isMobile: this.ctx.isMobile, - isEdit: this.ctx.isEdit, - subscriptionApi: this.ctx.subscriptionApi, - detectChanges: this.ctx.detectChanges, - settings: this.ctx.settings - } as WidgetContext; - }; - - private updateChart = () => { - if (this.flot && this.ctx.defaultSubscription.data.length) { - this.flot.update(); - } - }; - - private resize = () => { - if (this.flot) { - this.flot.resize(); - } - }; - - private reset() { - if (this.resize$) { - this.resize$.disconnect(); - } - if (this.subscription) { - this.subscription.unsubscribe(); - } - if (this.flot) { - this.flot.destroy(); - } - } - - private onDataUpdateError(e: any) { - const exceptionData = this.utils.parseException(e); - let errorText = exceptionData.name; - if (exceptionData.message) { - errorText += ': ' + exceptionData.message; - } - console.error(errorText); - } - - private onDataUpdated() { - this.isDataOnlyNumbers(); - if (this.isNumericData) { - if (this.chartInited) { - if (this.flot) { - this.flot.update(); - } - } else { - this.initChart(); - } - } - } - - private initChart() { - this.chartInited = true; - this.flotCtx.$container = $(this.statisticChart.nativeElement); - this.resize$.observe(this.statisticChart.nativeElement); - this.flot = new TbFlot(this.flotCtx as WidgetContext, 'line'); - this.flot.update(); - } - - private isDataOnlyNumbers() { - if (this.general) { - this.isNumericData = true; - return; - } - this.dataSource.data = this.subscription.data.length ? this.subscription.data[0].data : []; - if (this.dataSource.data.length && !this.dataTypeDefined) { - this.dataTypeDefined = true; - this.isNumericData = this.dataSource.data.every(data => !isNaN(+data[1])); - } - } - - - private changeSubscription(subscriptionInfo: SubscriptionInfo[]) { - if (this.subscription) { - this.reset(); - } - if (this.ctx.datasources[0].entity) { - this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.timeseries, subscriptionInfo, this.subscriptionOptions, - false, true).subscribe(subscription => { - this.dataTypeDefined = false; - this.subscription = subscription; - this.isDataOnlyNumbers(); - this.legendData = this.subscription.legendData; - this.flotCtx.defaultSubscription = subscription; - this.resize$ = new ResizeObserver(() => { - this.resize(); - }); - this.ctx.detectChanges(); - if (this.isNumericData) { - this.initChart(); - } - }); - } - } -} 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 deleted file mode 100644 index f8f1c99289..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ /dev/null @@ -1,1307 +0,0 @@ -/// -/// 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 { helpBaseUrl, ValueTypeData } from '@shared/models/constants'; -import { AttributeData } from '@shared/models/telemetry/telemetry.models'; - -export const noLeadTrailSpacesRegex = /^\S+(?: \S+)*$/; -export const integerRegex = /^[-+]?\d+$/; -export const nonZeroFloat = /^-?(?!0(\.0+)?$)\d+(\.\d+)?$/; - -export enum StorageTypes { - MEMORY = 'memory', - FILE = 'file', - SQLITE = 'sqlite' -} - -export enum DeviceGatewayStatus { - EXCEPTION = 'EXCEPTION' -} - -export enum GatewayLogLevel { - NONE = 'NONE', - CRITICAL = 'CRITICAL', - ERROR = 'ERROR', - WARNING = 'WARNING', - INFO = 'INFO', - DEBUG = 'DEBUG', - TRACE = 'TRACE' -} - -export enum PortLimits { - MIN = 1, - MAX = 65535 -} - -export const GatewayStatus = { - ...GatewayLogLevel, - ...DeviceGatewayStatus -}; - -export type GatewayStatus = DeviceGatewayStatus | GatewayLogLevel; - -export enum LogSavingPeriod { - days = 'D', - hours = 'H', - minutes = 'M', - seconds = 'S' -} - -export enum LocalLogsConfigs { - service = 'service', - connector = 'connector', - converter = 'converter', - tb_connection = 'tb_connection', - storage = 'storage', - extension = 'extension' -} - -export const LocalLogsConfigTranslateMap = new Map([ - [LocalLogsConfigs.service, 'Service'], - [LocalLogsConfigs.connector, 'Connector'], - [LocalLogsConfigs.converter, 'Converter'], - [LocalLogsConfigs.tb_connection, 'TB Connection'], - [LocalLogsConfigs.storage, 'Storage'], - [LocalLogsConfigs.extension, 'Extension'] -]); - -export const LogSavingPeriodTranslations = new Map( - [ - [LogSavingPeriod.days, 'gateway.logs.days'], - [LogSavingPeriod.hours, 'gateway.logs.hours'], - [LogSavingPeriod.minutes, 'gateway.logs.minutes'], - [LogSavingPeriod.seconds, 'gateway.logs.seconds'] - ] -); - -export const StorageTypesTranslationMap = new Map( - [ - [StorageTypes.MEMORY, 'gateway.storage-types.memory-storage'], - [StorageTypes.FILE, 'gateway.storage-types.file-storage'], - [StorageTypes.SQLITE, 'gateway.storage-types.sqlite'] - ] -); - -export enum SecurityTypes { - ACCESS_TOKEN = 'accessToken', - USERNAME_PASSWORD = 'usernamePassword', - TLS_ACCESS_TOKEN = 'tlsAccessToken', - TLS_PRIVATE_KEY = 'tlsPrivateKey' -} - -export const GecurityTypesTranslationsMap = new Map( - [ - [SecurityTypes.ACCESS_TOKEN, 'gateway.security-types.access-token'], - [SecurityTypes.USERNAME_PASSWORD, 'gateway.security-types.username-password'], - [SecurityTypes.TLS_ACCESS_TOKEN, 'gateway.security-types.tls-access-token'] - ] -); - -export interface GatewayAttributeData extends AttributeData { - skipSync?: boolean; -} - -export interface GatewayConnectorBase { - name: string; - type: ConnectorType; - configuration?: string; - logLevel: string; - key?: string; - class?: string; - mode?: ConfigurationModes; - configVersion?: string; - reportStrategy?: ReportStrategyConfig; - sendDataOnlyOnChange?: boolean; - ts?: number; -} - -export interface GatewayConnector extends GatewayConnectorBase { - configurationJson: BaseConfig; - basicConfig?: BaseConfig; -} - -export interface GatewayVersionedDefaultConfig { - legacy: GatewayConnector; - '3.5.2': GatewayConnector; -} - -export interface DataMapping { - topicFilter: string; - QoS: string | number; - converter: Converter; -} - -export interface RequestsMapping { - requestType: RequestType; - type: string; - details: string; -} - -export interface OpcUaMapping { - deviceNodePattern?: string; - deviceNamePattern?: string; - deviceProfileExpression?: string; -} - -export type MappingValue = DataMapping | RequestsMapping | OpcUaMapping; - -export interface ServerConfig { - name: string; - url: string; - timeoutInMillis: number; - scanPeriodInMillis: number; - pollPeriodInMillis: number; - enableSubscriptions: boolean; - subCheckPeriodInMillis: number; - showMap: boolean; - security: string; - identity: ConnectorSecurity; -} - -export interface BrokerConfig { - name: string; - host: string; - port: number; - version: number; - clientId: string; - maxNumberOfWorkers: number; - maxMessageNumberPerWorker: number; - security: ConnectorSecurity; -} - -export interface ConnectorSecurity { - type: SecurityType; - username?: string; - password?: string; - pathToCACert?: string; - pathToPrivateKey?: string; - pathToClientCert?: string; - mode?: ModeType; -} - -export enum GatewayVersion { - Current = '3.5.2', - Legacy = 'legacy' -} - -export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | ConverterConnectorMapping; - -export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue; - -export type ConnectorBaseConfig = ConnectorBaseConfig_v3_5_2 | ConnectorLegacyConfig; - -export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCLegacyBasicConfig | ModbusBasicConfig; - -export type ConnectorBaseConfig_v3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2 | OPCBasicConfig_v3_5_2; - -export interface ConnectorBaseInfo { - name: string; - id: string; - enableRemoteLogging: boolean; - logLevel: GatewayLogLevel; - configVersion: string | number; - reportStrategy?: ReportStrategyConfig; -} - -export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig; - -export interface MQTTBasicConfig_v3_5_2 { - mapping: ConverterConnectorMapping[]; - requestsMapping: Record | RequestMappingData[] | RequestMappingValue[]; - broker: BrokerConfig; - workers?: WorkersConfig; -} - -export interface MQTTLegacyBasicConfig { - mapping: LegacyConverterConnectorMapping[]; - broker: BrokerConfig; - workers?: WorkersConfig; - connectRequests: LegacyRequestMappingData[]; - disconnectRequests: LegacyRequestMappingData[]; - attributeRequests: LegacyRequestMappingData[]; - attributeUpdates: LegacyRequestMappingData[]; - serverSideRpc: LegacyRequestMappingData[]; -} - -export type OPCBasicConfig = OPCBasicConfig_v3_5_2 | OPCLegacyBasicConfig; - -export interface OPCBasicConfig_v3_5_2 { - mapping: DeviceConnectorMapping[]; - server: ServerConfig; -} - -export interface OPCLegacyBasicConfig { - server: LegacyServerConfig; -} - -export interface LegacyServerConfig extends Omit { - mapping: LegacyDeviceConnectorMapping[]; - disableSubscriptions: boolean; -} - -export type ModbusBasicConfig = ModbusBasicConfig_v3_5_2 | ModbusLegacyBasicConfig; - -export interface ModbusBasicConfig_v3_5_2 { - master: ModbusMasterConfig; - slave: ModbusSlave; -} - -export interface ModbusLegacyBasicConfig { - master: ModbusMasterConfig; - slave: ModbusLegacySlave; -} - -export interface WorkersConfig { - maxNumberOfWorkers: number; - maxMessageNumberPerWorker: number; -} - -export interface ConnectorDeviceInfo { - deviceNameExpression: string; - deviceNameExpressionSource: SourceType | OPCUaSourceType; - deviceProfileExpression: string; - deviceProfileExpressionSource: SourceType | OPCUaSourceType; -} - -export interface Attribute { - key: string; - type: string; - value: string; -} - -export interface LegacyAttribute { - key: string; - path: string; -} - -export interface Timeseries { - key: string; - type: string; - value: string; -} - -export interface LegacyTimeseries { - key: string; - path: string; -} - -export interface RpcArgument { - type: string; - value: number | string | boolean; -} - -export interface RpcMethod { - method: string; - arguments: RpcArgument[]; -} - -export interface LegacyRpcMethod { - method: string; - arguments: unknown[]; -} - -export interface AttributesUpdate { - key: string; - type: string; - value: string; -} - -export interface LegacyDeviceAttributeUpdate { - attributeOnThingsBoard: string; - attributeOnDevice: string; -} - -export interface Converter { - type: ConvertorType; - deviceInfo?: ConnectorDeviceInfo; - sendDataOnlyOnChange: boolean; - timeout: number; - attributes?: Attribute[]; - timeseries?: Timeseries[]; - extension?: string; - cached?: boolean; - extensionConfig?: Record; -} - -export interface LegacyConverter extends Converter { - deviceNameJsonExpression?: string; - deviceTypeJsonExpression?: string; - deviceNameTopicExpression?: string; - deviceTypeTopicExpression?: string; - deviceNameExpression?: string; - deviceNameExpressionSource?: string; - deviceTypeExpression?: string; - deviceProfileExpression?: string; - deviceProfileExpressionSource?: string; - ['extension-config']?: Record; -} - -export interface ConverterConnectorMapping { - topicFilter: string; - subscriptionQos?: string | number; - converter: Converter; -} - -export interface LegacyConverterConnectorMapping { - topicFilter: string; - subscriptionQos?: string | number; - converter: LegacyConverter; -} - -export type ConverterMappingFormValue = Omit & { - converter: { - type: ConvertorType; - } & Record; -}; - -export interface DeviceConnectorMapping { - deviceNodePattern: string; - deviceNodeSource: OPCUaSourceType; - deviceInfo: ConnectorDeviceInfo; - attributes?: Attribute[]; - timeseries?: Timeseries[]; - rpc_methods?: RpcMethod[]; - attributes_updates?: AttributesUpdate[]; -} - -export interface LegacyDeviceConnectorMapping { - deviceNamePattern: string; - deviceNodePattern: string; - deviceTypePattern: string; - attributes?: LegacyAttribute[]; - timeseries?: LegacyTimeseries[]; - rpc_methods?: LegacyRpcMethod[]; - attributes_updates?: LegacyDeviceAttributeUpdate[]; -} - -export enum ConnectorType { - MQTT = 'mqtt', - MODBUS = 'modbus', - GRPC = 'grpc', - OPCUA = 'opcua', - BLE = 'ble', - REQUEST = 'request', - CAN = 'can', - BACNET = 'bacnet', - ODBC = 'odbc', - REST = 'rest', - SNMP = 'snmp', - FTP = 'ftp', - SOCKET = 'socket', - XMPP = 'xmpp', - OCPP = 'ocpp', - CUSTOM = 'custom' -} - -export const GatewayConnectorDefaultTypesTranslatesMap = new Map([ - [ConnectorType.MQTT, 'MQTT'], - [ConnectorType.MODBUS, 'MODBUS'], - [ConnectorType.GRPC, 'GRPC'], - [ConnectorType.OPCUA, 'OPCUA'], - [ConnectorType.BLE, 'BLE'], - [ConnectorType.REQUEST, 'REQUEST'], - [ConnectorType.CAN, 'CAN'], - [ConnectorType.BACNET, 'BACNET'], - [ConnectorType.ODBC, 'ODBC'], - [ConnectorType.REST, 'REST'], - [ConnectorType.SNMP, 'SNMP'], - [ConnectorType.FTP, 'FTP'], - [ConnectorType.SOCKET, 'SOCKET'], - [ConnectorType.XMPP, 'XMPP'], - [ConnectorType.OCPP, 'OCPP'], - [ConnectorType.CUSTOM, 'CUSTOM'] -]); - -export interface RPCCommand { - command: string; - params: any; - time: number; -} - -export const ModbusFunctionCodeTranslationsMap = new Map([ - [1, 'gateway.function-codes.read-coils'], - [2, 'gateway.function-codes.read-discrete-inputs'], - [3, 'gateway.function-codes.read-multiple-holding-registers'], - [4, 'gateway.function-codes.read-input-registers'], - [5, 'gateway.function-codes.write-single-coil'], - [6, 'gateway.function-codes.write-single-holding-register'], - [15, 'gateway.function-codes.write-multiple-coils'], - [16, 'gateway.function-codes.write-multiple-holding-registers'] -]); - -export enum BACnetRequestTypes { - WriteProperty = 'writeProperty', - ReadProperty = 'readProperty' -} - -export const BACnetRequestTypesTranslates = new Map([ - [BACnetRequestTypes.WriteProperty, 'gateway.rpc.write-property'], - [BACnetRequestTypes.ReadProperty, 'gateway.rpc.read-property'] -]); - -export enum BACnetObjectTypes { - BinaryInput = 'binaryInput', - BinaryOutput = 'binaryOutput', - AnalogInput = 'analogInput', - AnalogOutput = 'analogOutput', - BinaryValue = 'binaryValue', - AnalogValue = 'analogValue' -} - -export const BACnetObjectTypesTranslates = new Map([ - [BACnetObjectTypes.AnalogOutput, 'gateway.rpc.analog-output'], - [BACnetObjectTypes.AnalogInput, 'gateway.rpc.analog-input'], - [BACnetObjectTypes.BinaryOutput, 'gateway.rpc.binary-output'], - [BACnetObjectTypes.BinaryInput, 'gateway.rpc.binary-input'], - [BACnetObjectTypes.BinaryValue, 'gateway.rpc.binary-value'], - [BACnetObjectTypes.AnalogValue, 'gateway.rpc.analog-value'] -]); - -export enum BLEMethods { - WRITE = 'write', - READ = 'read', - SCAN = 'scan' -} - -export const BLEMethodsTranslates = new Map([ - [BLEMethods.WRITE, 'gateway.rpc.write'], - [BLEMethods.READ, 'gateway.rpc.read'], - [BLEMethods.SCAN, 'gateway.rpc.scan'], -]); - -export enum CANByteOrders { - LITTLE = 'LITTLE', - BIG = 'BIG' -} - -export enum SocketMethodProcessings { - WRITE = 'write' -} - -export const SocketMethodProcessingsTranslates = new Map([ - [SocketMethodProcessings.WRITE, 'gateway.rpc.write'] -]); - -export enum SNMPMethods { - SET = 'set', - MULTISET = 'multiset', - GET = 'get', - BULKWALK = 'bulkwalk', - TABLE = 'table', - MULTIGET = 'multiget', - GETNEXT = 'getnext', - BULKGET = 'bulkget', - WALKS = 'walk' -} - -export const SNMPMethodsTranslations = new Map([ - [SNMPMethods.SET, 'gateway.rpc.set'], - [SNMPMethods.MULTISET, 'gateway.rpc.multiset'], - [SNMPMethods.GET, 'gateway.rpc.get'], - [SNMPMethods.BULKWALK, 'gateway.rpc.bulk-walk'], - [SNMPMethods.TABLE, 'gateway.rpc.table'], - [SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'], - [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], - [SNMPMethods.BULKGET, 'gateway.rpc.bulk-get'], - [SNMPMethods.WALKS, 'gateway.rpc.walk'] -]); - -export enum HTTPMethods { - CONNECT = 'CONNECT', - DELETE = 'DELETE', - GET = 'GET', - HEAD = 'HEAD', - OPTIONS = 'OPTIONS', - PATCH = 'PATCH', - POST = 'POST', - PUT = 'PUT', - TRACE = 'TRACE' - -} - -export enum SocketEncodings { - UTF_8 = 'utf-8' -} - -export interface RPCTemplate { - name?: string; - config: RPCTemplateConfig; -} - -export interface RPCTemplateConfig { - [key: string]: any; -} - -export interface RPCTemplateConfigMQTT { - methodFilter: string; - requestTopicExpression: string; - responseTopicExpression?: string; - responseTimeout?: number; - valueExpression: string; - withResponse: boolean; -} - -export interface RPCTemplateConfigModbus { - tag: string; - type: ModbusDataType; - functionCode?: number; - objectsCount: number; - address: number; - value?: string; -} - -export interface RPCTemplateConfigOPC { - method: string; - arguments: RpcArgument[]; -} - -export interface OPCTypeValue { - type: MappingValueType; - boolean?: boolean; - double?: number; - integer?: number; - string?: string; -} - -export interface SaveRPCTemplateData { - config: RPCTemplateConfig; - templates: Array; -} - -export interface LogLink { - name: string; - key: string; - filterFn?: (arg: any) => boolean; -} - -export interface GatewayLogData { - ts: number; - key: string; - message: string; - status: GatewayStatus; -} - -export interface AddConnectorConfigData { - dataSourceData: Array; - gatewayVersion: string; -} - -export interface CreatedConnectorConfigData { - type: ConnectorType; - name: string; - logLevel: GatewayLogLevel; - useDefaults: boolean; - sendDataOnlyOnChange: boolean; - configurationJson?: {[key: string]: any}; -} - -export interface MappingDataKey { - key: string; - value: any; - type: MappingValueType; -} - -export interface RpcMethodsMapping { - method: string; - arguments: Array; -} - -export interface MappingInfo { - mappingType: MappingType; - value: {[key: string]: any}; - buttonTitle: string; -} - -export interface ModbusSlaveInfo { - value: Slave; - buttonTitle: string; - hideNewFields: boolean; -} - -export enum ConfigurationModes { - BASIC = 'basic', - ADVANCED = 'advanced' -} - -export enum SecurityType { - ANONYMOUS = 'anonymous', - BASIC = 'basic', - CERTIFICATES = 'certificates' -} - -export enum ReportStrategyType { - OnChange = 'ON_CHANGE', - OnReportPeriod = 'ON_REPORT_PERIOD', - OnChangeOrReportPeriod = 'ON_CHANGE_OR_REPORT_PERIOD' -} - -export enum ReportStrategyDefaultValue { - Connector = 60000, - Device = 30000, - Key = 15000 -} - -export const ReportStrategyTypeTranslationsMap = new Map( - [ - [ReportStrategyType.OnChange, 'gateway.report-strategy.on-change'], - [ReportStrategyType.OnReportPeriod, 'gateway.report-strategy.on-report-period'], - [ReportStrategyType.OnChangeOrReportPeriod, 'gateway.report-strategy.on-change-or-report-period'] - ] -); - -export enum ModeType { - NONE = 'None', - SIGN = 'Sign', - SIGNANDENCRYPT = 'SignAndEncrypt' -} - -export const SecurityTypeTranslationsMap = new Map( - [ - [SecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'], - [SecurityType.BASIC, 'gateway.broker.security-types.basic'], - [SecurityType.CERTIFICATES, 'gateway.broker.security-types.certificates'] - ] -); - -export enum RestSecurityType { - ANONYMOUS = 'anonymous', - BASIC = 'basic', -} - -export const RestSecurityTypeTranslationsMap = 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 }, - { name: 5, value: 5 } -]; - -export enum MappingType { - DATA = 'data', - REQUESTS = 'requests', - OPCUA = 'OPCua' -} - -export const MappingTypeTranslationsMap = new Map( - [ - [MappingType.DATA, 'gateway.data-mapping'], - [MappingType.REQUESTS, 'gateway.requests-mapping'], - [MappingType.OPCUA, 'gateway.data-mapping'] - ] -); - -export const MappingHintTranslationsMap = new Map( - [ - [MappingType.DATA, 'gateway.data-mapping-hint'], - [MappingType.OPCUA, 'gateway.opcua-data-mapping-hint'], - [MappingType.REQUESTS, 'gateway.requests-mapping-hint'] - ] -); - -export const HelpLinkByMappingTypeMap = new Map( - [ - [MappingType.DATA, helpBaseUrl + '/docs/iot-gateway/config/mqtt/#section-mapping'], - [MappingType.OPCUA, helpBaseUrl + '/docs/iot-gateway/config/opc-ua/#section-mapping'], - [MappingType.REQUESTS, helpBaseUrl + '/docs/iot-gateway/config/mqtt/#requests-mapping'] - ] -); - -export const QualityTypes = [0, 1 ,2]; - -export const QualityTypeTranslationsMap = new Map( - [ - [0, 'gateway.qos.at-most-once'], - [1, 'gateway.qos.at-least-once'], - [2, 'gateway.qos.exactly-once'] - ] -); - -export enum ConvertorType { - JSON = 'json', - BYTES = 'bytes', - CUSTOM = 'custom' -} - -export const ConvertorTypeTranslationsMap = new Map( - [ - [ConvertorType.JSON, 'gateway.JSON'], - [ConvertorType.BYTES, 'gateway.bytes'], - [ConvertorType.CUSTOM, 'gateway.custom'] - ] -); - -export enum SourceType { - MSG = 'message', - TOPIC = 'topic', - CONST = 'constant' -} - -export enum OPCUaSourceType { - PATH = 'path', - IDENTIFIER = 'identifier', - CONST = 'constant' -} - -export enum DeviceInfoType { - FULL = 'full', - PARTIAL = 'partial' -} - -export const SourceTypeTranslationsMap = new Map( - [ - [SourceType.MSG, 'gateway.source-type.msg'], - [SourceType.TOPIC, 'gateway.source-type.topic'], - [SourceType.CONST, 'gateway.source-type.const'], - [OPCUaSourceType.PATH, 'gateway.source-type.path'], - [OPCUaSourceType.IDENTIFIER, 'gateway.source-type.identifier'], - [OPCUaSourceType.CONST, 'gateway.source-type.const'] - ] -); - -export interface RequestMappingValue { - requestType: RequestType; - requestValue: RequestMappingData; -} - -export interface RequestMappingFormValue { - requestType: RequestType; - requestValue: Record; -} - -export type RequestMappingData = ConnectRequest | DisconnectRequest | AttributeRequest | AttributeUpdate | ServerSideRpc; - -export type LegacyRequestMappingData = - LegacyConnectRequest - | LegacyDisconnectRequest - | LegacyAttributeRequest - | LegacyAttributeUpdate - | LegacyServerSideRpc; - -export interface ConnectRequest { - topicFilter: string; - deviceInfo: ConnectorDeviceInfo; -} - -export interface DisconnectRequest { - topicFilter: string; - deviceInfo: ConnectorDeviceInfo; -} - -export interface AttributeRequest { - retain: boolean; - topicFilter: string; - deviceInfo: ConnectorDeviceInfo; - attributeNameExpressionSource: SourceType; - attributeNameExpression: string; - topicExpression: string; - valueExpression: string; -} - -export interface AttributeUpdate { - retain: boolean; - deviceNameFilter: string; - attributeFilter: string; - topicExpression: string; - valueExpression: string; -} - -export interface ServerSideRpc { - type: ServerSideRpcType; - deviceNameFilter: string; - methodFilter: string; - requestTopicExpression: string; - responseTopicExpression?: string; - responseTopicQoS?: number; - responseTimeout?: number; - valueExpression: string; -} - -export enum ServerSideRpcType { - WithResponse = 'twoWay', - WithoutResponse = 'oneWay' -} - -export interface LegacyConnectRequest { - topicFilter: string; - deviceNameJsonExpression?: string; - deviceNameTopicExpression?: string; -} - -interface LegacyDisconnectRequest { - topicFilter: string; - deviceNameJsonExpression?: string; - deviceNameTopicExpression?: string; -} - -interface LegacyAttributeRequest { - retain: boolean; - topicFilter: string; - deviceNameJsonExpression: string; - attributeNameJsonExpression: string; - topicExpression: string; - valueExpression: string; -} - -interface LegacyAttributeUpdate { - retain: boolean; - deviceNameFilter: string; - attributeFilter: string; - topicExpression: string; - valueExpression: string; -} - -interface LegacyServerSideRpc { - deviceNameFilter: string; - methodFilter: string; - requestTopicExpression: string; - responseTopicExpression?: string; - responseTimeout?: number; - valueExpression: string; -} - -export enum RequestType { - CONNECT_REQUEST = 'connectRequests', - DISCONNECT_REQUEST = 'disconnectRequests', - ATTRIBUTE_REQUEST = 'attributeRequests', - ATTRIBUTE_UPDATE = 'attributeUpdates', - SERVER_SIDE_RPC = 'serverSideRpc' -} - -export const RequestTypesTranslationsMap = new Map( - [ - [RequestType.CONNECT_REQUEST, 'gateway.request.connect-request'], - [RequestType.DISCONNECT_REQUEST, 'gateway.request.disconnect-request'], - [RequestType.ATTRIBUTE_REQUEST, 'gateway.request.attribute-request'], - [RequestType.ATTRIBUTE_UPDATE, 'gateway.request.attribute-update'], - [RequestType.SERVER_SIDE_RPC, 'gateway.request.rpc-connection'], - ] -); - -export enum MappingKeysType { - ATTRIBUTES = 'attributes', - TIMESERIES = 'timeseries', - CUSTOM = 'extensionConfig', - RPC_METHODS = 'rpc_methods', - ATTRIBUTES_UPDATES = 'attributes_updates' -} - -export const MappingKeysPanelTitleTranslationsMap = new Map( - [ - [MappingKeysType.ATTRIBUTES, 'gateway.attributes'], - [MappingKeysType.TIMESERIES, 'gateway.timeseries'], - [MappingKeysType.CUSTOM, 'gateway.keys'], - [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.attribute-updates'], - [MappingKeysType.RPC_METHODS, 'gateway.rpc-methods'] - ] -); - -export const MappingKeysAddKeyTranslationsMap = new Map( - [ - [MappingKeysType.ATTRIBUTES, 'gateway.add-attribute'], - [MappingKeysType.TIMESERIES, 'gateway.add-timeseries'], - [MappingKeysType.CUSTOM, 'gateway.add-key'], - [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.add-attribute-update'], - [MappingKeysType.RPC_METHODS, 'gateway.add-rpc-method'] - ] -); - -export const MappingKeysDeleteKeyTranslationsMap = new Map( - [ - [MappingKeysType.ATTRIBUTES, 'gateway.delete-attribute'], - [MappingKeysType.TIMESERIES, 'gateway.delete-timeseries'], - [MappingKeysType.CUSTOM, 'gateway.delete-key'], - [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.delete-attribute-update'], - [MappingKeysType.RPC_METHODS, 'gateway.delete-rpc-method'] - ] -); - -export const MappingKeysNoKeysTextTranslationsMap = new Map( - [ - [MappingKeysType.ATTRIBUTES, 'gateway.no-attributes'], - [MappingKeysType.TIMESERIES, 'gateway.no-timeseries'], - [MappingKeysType.CUSTOM, 'gateway.no-keys'], - [MappingKeysType.ATTRIBUTES_UPDATES, 'gateway.no-attribute-updates'], - [MappingKeysType.RPC_METHODS, 'gateway.no-rpc-methods'] - ] -); - -export enum ServerSideRPCType { - ONE_WAY = 'oneWay', - TWO_WAY = 'twoWay' -} - -export enum MappingValueType { - STRING = 'string', - INTEGER = 'integer', - DOUBLE = 'double', - BOOLEAN = 'boolean' -} - -export enum ModifierType { - DIVIDER = 'divider', - MULTIPLIER = 'multiplier', -} - -export const ModifierTypesMap = new Map( - [ - [ - ModifierType.DIVIDER, - { - name: 'gateway.divider', - icon: 'mdi:division' - } - ], - [ - ModifierType.MULTIPLIER, - { - name: 'gateway.multiplier', - icon: 'mdi:multiplication' - } - ], - ] -); - -export const mappingValueTypesMap = new Map( - [ - [ - MappingValueType.STRING, - { - name: 'value.string', - icon: 'mdi:format-text' - } - ], - [ - MappingValueType.INTEGER, - { - name: 'value.integer', - icon: 'mdi:numeric' - } - ], - [ - MappingValueType.DOUBLE, - { - name: 'value.double', - icon: 'mdi:numeric' - } - ], - [ - MappingValueType.BOOLEAN, - { - name: 'value.boolean', - icon: 'mdi:checkbox-marked-outline' - } - ] - ] -); - -export const DataConversionTranslationsMap = new Map( - [ - [ConvertorType.JSON, 'gateway.JSON-hint'], - [ConvertorType.BYTES, 'gateway.bytes-hint'], - [ConvertorType.CUSTOM, 'gateway.custom-hint'] - ] -); - -export enum SecurityPolicy { - BASIC128 = 'Basic128Rsa15', - BASIC256 = 'Basic256', - BASIC256SHA = 'Basic256Sha256' -} - -export const SecurityPolicyTypes = [ - { value: SecurityPolicy.BASIC128, name: 'Basic128RSA15' }, - { value: SecurityPolicy.BASIC256, name: 'Basic256' }, - { value: SecurityPolicy.BASIC256SHA, name: 'Basic256SHA256' } -]; - -export enum ModbusProtocolType { - TCP = 'tcp', - UDP = 'udp', - Serial = 'serial', -} - -export const ModbusProtocolLabelsMap = new Map( - [ - [ModbusProtocolType.TCP, 'TCP'], - [ModbusProtocolType.UDP, 'UDP'], - [ModbusProtocolType.Serial, 'Serial'], - ] -); - -export enum ModbusMethodType { - SOCKET = 'socket', - RTU = 'rtu', -} - -export enum ModbusSerialMethodType { - RTU = 'rtu', - ASCII = 'ascii', -} - -export const ModbusMethodLabelsMap = new Map( - [ - [ModbusMethodType.SOCKET, 'Socket'], - [ModbusMethodType.RTU, 'RTU'], - [ModbusSerialMethodType.ASCII, 'ASCII'], - ] -); - -export const ModbusByteSizes = [5, 6, 7 ,8]; - -export enum ModbusParity { - Even = 'E', - Odd = 'O', - None = 'N' -} - -export const ModbusParityLabelsMap = new Map( - [ - [ModbusParity.Even, 'Even'], - [ModbusParity.Odd, 'Odd'], - [ModbusParity.None, 'None'], - ] -); - -export enum ModbusOrderType { - BIG = 'BIG', - LITTLE = 'LITTLE', -} - -export enum ModbusRegisterType { - HoldingRegisters = 'holding_registers', - CoilsInitializer = 'coils_initializer', - InputRegisters = 'input_registers', - DiscreteInputs = 'discrete_inputs' -} - -export const ModbusRegisterTranslationsMap = new Map( - [ - [ModbusRegisterType.HoldingRegisters, 'gateway.holding_registers'], - [ModbusRegisterType.CoilsInitializer, 'gateway.coils_initializer'], - [ModbusRegisterType.InputRegisters, 'gateway.input_registers'], - [ModbusRegisterType.DiscreteInputs, 'gateway.discrete_inputs'] - ] -); - -export enum ModbusDataType { - STRING = 'string', - BYTES = 'bytes', - BITS = 'bits', - INT8 = '8int', - UINT8 = '8uint', - FLOAT8 = '8float', - INT16 = '16int', - UINT16 = '16uint', - FLOAT16 = '16float', - INT32 = '32int', - UINT32 = '32uint', - FLOAT32 = '32float', - INT64 = '64int', - UINT64 = '64uint', - FLOAT64 = '64float' -} - -export const ModbusEditableDataTypes = [ModbusDataType.BYTES, ModbusDataType.BITS, ModbusDataType.STRING]; - -export enum ModbusObjectCountByDataType { - '8int' = 1, - '8uint' = 1, - '8float' = 1, - '16int' = 1, - '16uint' = 1, - '16float' = 1, - '32int' = 2, - '32uint' = 2, - '32float' = 2, - '64int' = 4, - '64uint' = 4, - '64float' = 4, -} - -export enum ModbusValueKey { - ATTRIBUTES = 'attributes', - TIMESERIES = 'timeseries', - ATTRIBUTES_UPDATES = 'attributeUpdates', - RPC_REQUESTS = 'rpc', -} - -export const ModbusKeysPanelTitleTranslationsMap = new Map( - [ - [ModbusValueKey.ATTRIBUTES, 'gateway.attributes'], - [ModbusValueKey.TIMESERIES, 'gateway.timeseries'], - [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.attribute-updates'], - [ModbusValueKey.RPC_REQUESTS, 'gateway.rpc-requests'] - ] -); - -export const ModbusKeysAddKeyTranslationsMap = new Map( - [ - [ModbusValueKey.ATTRIBUTES, 'gateway.add-attribute'], - [ModbusValueKey.TIMESERIES, 'gateway.add-timeseries'], - [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.add-attribute-update'], - [ModbusValueKey.RPC_REQUESTS, 'gateway.add-rpc-request'] - ] -); - -export const ModbusKeysDeleteKeyTranslationsMap = new Map( - [ - [ModbusValueKey.ATTRIBUTES, 'gateway.delete-attribute'], - [ModbusValueKey.TIMESERIES, 'gateway.delete-timeseries'], - [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.delete-attribute-update'], - [ModbusValueKey.RPC_REQUESTS, 'gateway.delete-rpc-request'] - ] -); - -export const ModbusKeysNoKeysTextTranslationsMap = new Map( - [ - [ModbusValueKey.ATTRIBUTES, 'gateway.no-attributes'], - [ModbusValueKey.TIMESERIES, 'gateway.no-timeseries'], - [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.no-attribute-updates'], - [ModbusValueKey.RPC_REQUESTS, 'gateway.no-rpc-requests'] - ] -); - -export interface ModbusMasterConfig { - slaves: Slave[]; -} - -export interface LegacySlaveConfig extends Omit { - sendDataOnlyOnChange: boolean; -} - -export interface SlaveConfig { - name: string; - host?: string; - port: string | number; - serialPort?: string; - type: ModbusProtocolType; - method: ModbusMethodType; - timeout: number; - byteOrder: ModbusOrderType; - wordOrder: ModbusOrderType; - retries: boolean; - retryOnEmpty: boolean; - retryOnInvalid: boolean; - pollPeriod: number; - unitId: number; - deviceName: string; - deviceType?: string; - reportStrategy: ReportStrategyConfig; - connectAttemptTimeMs: number; - connectAttemptCount: number; - waitAfterFailedAttemptsMs: number; - attributes: ModbusValue[]; - timeseries: ModbusValue[]; - attributeUpdates: ModbusValue[]; - rpc: ModbusValue[]; - security?: ModbusSecurity; - baudrate?: number; - stopbits?: number; - bytesize?: number; - parity?: ModbusParity; - strict?: boolean; -} - -export interface ModbusValue { - tag: string; - type: ModbusDataType; - functionCode?: number; - objectsCount: number; - address: number; - value?: string; - reportStrategy?: ReportStrategyConfig; - multiplier?: number; - divider?: number; -} - -export interface ModbusFormValue extends ModbusValue { - modifierType?: ModifierType; - modifierValue?: string; -} - -export interface ModbusSecurity { - certfile?: string; - keyfile?: string; - password?: string; - server_hostname?: string; - reqclicert?: boolean; -} - -export interface ModbusSlave { - host?: string; - type: ModbusProtocolType; - method: ModbusMethodType; - unitId: number; - serialPort?: string; - baudrate?: number; - deviceName: string; - deviceType: string; - pollPeriod: number; - sendDataToThingsBoard: boolean; - byteOrder: ModbusOrderType; - wordOrder: ModbusOrderType; - identity: ModbusIdentity; - values?: ModbusValuesState; - port: string | number; - security: ModbusSecurity; -} - -export interface ModbusLegacySlave extends Omit { - values?: ModbusLegacyRegisterValues; -} - -export type ModbusValuesState = ModbusRegisterValues | ModbusValues; - -export interface ModbusLegacyRegisterValues { - holding_registers: ModbusValues[]; - coils_initializer: ModbusValues[]; - input_registers: ModbusValues[]; - discrete_inputs: ModbusValues[]; -} - -export interface ModbusRegisterValues { - holding_registers: ModbusValues; - coils_initializer: ModbusValues; - input_registers: ModbusValues; - discrete_inputs: ModbusValues; -} - -export interface ModbusValues { - attributes: ModbusValue[]; - timeseries: ModbusValue[]; - attributeUpdates: ModbusValue[]; - rpc: ModbusValue[]; -} - -export interface ModbusIdentity { - vendorName?: string; - productCode?: string; - vendorUrl?: string; - productName?: string; - modelName?: string; -} - -export interface ReportStrategyConfig { - type: ReportStrategyType; - reportPeriod?: number; -} - -export const ModbusBaudrates = [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts deleted file mode 100644 index 27b5285166..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts +++ /dev/null @@ -1,41 +0,0 @@ -/// -/// 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 { Pipe, PipeTransform } from '@angular/core'; -import { - MappingValueType, - OPCUaSourceType, - SourceType -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -@Pipe({ - name: 'getGatewayHelpLink', - standalone: true, -}) -export class GatewayHelpLinkPipe implements PipeTransform { - transform(field: string, sourceType: SourceType | OPCUaSourceType, sourceTypes?: Array ): string { - if (!sourceTypes || sourceTypes?.includes(OPCUaSourceType.PATH)) { - if (sourceType !== OPCUaSourceType.CONST) { - return `widget/lib/gateway/${field}-${sourceType}_fn`; - } else { - return; - } - } else if (field === 'attributes' || field === 'timeseries') { - return 'widget/lib/gateway/attributes_timeseries_expressions_fn'; - } - return 'widget/lib/gateway/expressions_fn'; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe.ts deleted file mode 100644 index fcf5766c05..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe.ts +++ /dev/null @@ -1,42 +0,0 @@ -/// -/// 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 { Pipe, PipeTransform } from '@angular/core'; -import { PortLimits } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { AbstractControl } from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; - -@Pipe({ - name: 'getGatewayPortTooltip', - standalone: true, -}) -export class GatewayPortTooltipPipe implements PipeTransform { - - constructor(private translate: TranslateService) {} - - transform(portControl: AbstractControl): string { - if (portControl.hasError('required')) { - return this.translate.instant('gateway.port-required'); - } - if (portControl.hasError('min') || portControl.hasError('max')) { - return this.translate.instant('gateway.port-limits-error', { - min: PortLimits.MIN, - max: PortLimits.MAX, - }); - } - return ''; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/latest-version-config.pipe.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/latest-version-config.pipe.ts deleted file mode 100644 index 56484dde35..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/latest-version-config.pipe.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// -/// 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 { Pipe, PipeTransform } from '@angular/core'; -import { GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { - GatewayConnectorVersionMappingUtil -} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util'; - -@Pipe({ - name: 'isLatestVersionConfig', - standalone: true, -}) -export class LatestVersionConfigPipe implements PipeTransform { - transform(configVersion: number | string): boolean { - return GatewayConnectorVersionMappingUtil.parseVersion(configVersion) - >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts deleted file mode 100644 index fe507c1c46..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts +++ /dev/null @@ -1,51 +0,0 @@ -/// -/// 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 { - ConnectorType, - GatewayConnector, - ModbusBasicConfig, - MQTTBasicConfig, - OPCBasicConfig, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; -import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract'; -import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract'; -import { isNumber, isString } from '@core/utils'; - -export abstract class GatewayConnectorVersionMappingUtil { - - static getConfig(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { - switch(connector.type) { - case ConnectorType.MQTT: - return new MqttVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); - case ConnectorType.OPCUA: - return new OpcVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); - case ConnectorType.MODBUS: - return new ModbusVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); - default: - return connector; - } - } - - static parseVersion(version: string | number): number { - if (isNumber(version)) { - return version as number; - } - - return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts deleted file mode 100644 index eb1ba9033f..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts +++ /dev/null @@ -1,106 +0,0 @@ -/// -/// 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 { - LegacySlaveConfig, - ModbusDataType, - ModbusLegacyRegisterValues, - ModbusLegacySlave, - ModbusMasterConfig, - ModbusRegisterValues, - ModbusSlave, - ModbusValue, - ModbusValues, - ReportStrategyType, - SlaveConfig -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -export class ModbusVersionMappingUtil { - - static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { - return { - slaves: master.slaves.map((slave: LegacySlaveConfig) => { - const { sendDataOnlyOnChange, ...restSlave } = slave; - return { - ...restSlave, - deviceType: slave.deviceType ?? 'default', - reportStrategy: sendDataOnlyOnChange - ? { type: ReportStrategyType.OnChange } - : { type: ReportStrategyType.OnReportPeriod, reportPeriod: slave.pollPeriod } - }; - }) - }; - } - - static mapMasterToDowngradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { - return { - slaves: master.slaves.map((slave: SlaveConfig) => { - const { reportStrategy, ...restSlave } = slave; - return { - ...restSlave, - sendDataOnlyOnChange: reportStrategy?.type !== ReportStrategyType.OnReportPeriod - }; - }) - }; - } - - static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave { - if (!slave?.values) { - return slave as Omit; - } - const values = Object.keys(slave.values).reduce((acc, valueKey) => { - acc = { - ...acc, - [valueKey]: [ - slave.values[valueKey] - ] - }; - return acc; - }, {} as ModbusLegacyRegisterValues); - return { - ...slave, - values - }; - } - - static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave { - if (!slave?.values) { - return slave as Omit; - } - const values = Object.keys(slave.values).reduce((acc, valueKey) => { - acc = { - ...acc, - [valueKey]: this.mapValuesToUpgradedVersion(slave.values[valueKey][0]) - }; - return acc; - }, {} as ModbusRegisterValues); - return { - ...slave, - values - }; - } - - private static mapValuesToUpgradedVersion(registerValues: ModbusValues): ModbusValues { - return Object.keys(registerValues).reduce((acc, valueKey) => { - acc = { - ...acc, - [valueKey]: registerValues[valueKey].map((value: ModbusValue) => - ({ ...value, type: (value.type as string) === 'int' ? ModbusDataType.INT16 : value.type })) - }; - return acc; - }, {} as ModbusValues); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts deleted file mode 100644 index 23cc03dc4e..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts +++ /dev/null @@ -1,217 +0,0 @@ -/// -/// 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 { deleteNullProperties } from '@core/utils'; -import { - AttributeRequest, - ConnectorDeviceInfo, - Converter, - ConverterConnectorMapping, - ConvertorType, - LegacyConverter, - LegacyConverterConnectorMapping, - LegacyRequestMappingData, - RequestMappingData, - RequestType, - ServerSideRpc, - ServerSideRpcType, - SourceType -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -export class MqttVersionMappingUtil { - - static readonly mqttRequestTypeKeys = Object.values(RequestType); - static readonly mqttRequestMappingOldFields = - ['attributeNameJsonExpression', 'deviceNameJsonExpression', 'deviceNameTopicExpression', 'extension-config']; - static readonly mqttRequestMappingNewFields = - ['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; - - static mapMappingToUpgradedVersion( - mapping: LegacyConverterConnectorMapping[] - ): ConverterConnectorMapping[] { - return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { - const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); - - const newConverter = { - ...converter, - deviceInfo, - extensionConfig: converter.extensionConfig || converter['extension-config'] || null - }; - - this.cleanUpOldFields(newConverter); - - return { converter: newConverter, topicFilter, subscriptionQos }; - }) as ConverterConnectorMapping[]; - } - - static mapRequestsToUpgradedVersion( - requestMapping: Record - ): Record { - return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { - if (!requestMapping[key]) { - return acc; - } - - acc[key] = requestMapping[key].map(value => { - const newValue = this.mapRequestToUpgradedVersion(value as LegacyRequestMappingData, key); - - this.cleanUpOldFields(newValue as {}); - - return newValue; - }); - - return acc; - }, {}) as Record; - } - - static mapRequestsToDowngradedVersion( - requestsMapping: Record - ): Record { - return this.mqttRequestTypeKeys.reduce((acc, key) => { - if (!requestsMapping[key]) { - return acc; - } - - acc[key] = requestsMapping[key].map((value: RequestMappingData) => { - if (key === RequestType.SERVER_SIDE_RPC) { - delete (value as ServerSideRpc).type; - } - - const { attributeNameExpression, deviceInfo, ...rest } = value as AttributeRequest; - - const newValue = { - ...rest, - attributeNameJsonExpression: attributeNameExpression || null, - deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource !== SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, - deviceNameTopicExpression: deviceInfo?.deviceNameExpressionSource === SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, - }; - - this.cleanUpNewFields(newValue); - - return newValue; - }); - - return acc; - }, {}) as Record; - } - - static mapMappingToDowngradedVersion( - mapping: ConverterConnectorMapping[] - ): LegacyConverterConnectorMapping[] { - return mapping?.map((converterMapping: ConverterConnectorMapping) => { - const converter = this.mapConverterToDowngradedVersion(converterMapping.converter); - - this.cleanUpNewFields(converter as {}); - - return { converter, topicFilter: converterMapping.topicFilter }; - }); - } - - private static mapConverterToDowngradedVersion(converter: Converter): LegacyConverter { - const { deviceInfo, ...restConverter } = converter; - - return converter.type !== ConvertorType.BYTES ? { - ...restConverter, - deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource === SourceType.MSG ? deviceInfo.deviceNameExpression : null, - deviceTypeJsonExpression: - deviceInfo?.deviceProfileExpressionSource === SourceType.MSG ? deviceInfo.deviceProfileExpression : null, - deviceNameTopicExpression: - deviceInfo?.deviceNameExpressionSource !== SourceType.MSG - ? deviceInfo?.deviceNameExpression - : null, - deviceTypeTopicExpression: deviceInfo?.deviceProfileExpressionSource !== SourceType.MSG - ? deviceInfo?.deviceProfileExpression - : null, - } : { - ...restConverter, - deviceNameExpression: deviceInfo.deviceNameExpression, - deviceTypeExpression: deviceInfo.deviceProfileExpression, - ['extension-config']: converter.extensionConfig, - }; - } - - private static cleanUpOldFields(obj: Record): void { - this.mqttRequestMappingOldFields.forEach(field => delete obj[field]); - deleteNullProperties(obj); - } - - private static cleanUpNewFields(obj: Record): void { - this.mqttRequestMappingNewFields.forEach(field => delete obj[field]); - deleteNullProperties(obj); - } - - private static getTypeSourceByValue(value: string): SourceType { - if (value.includes('${')) { - return SourceType.MSG; - } - if (value.includes(`/`)) { - return SourceType.TOPIC; - } - return SourceType.CONST; - } - - private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { - const deviceNameExpression = converter.deviceNameExpression - || converter.deviceNameJsonExpression - || converter.deviceNameTopicExpression - || null; - const deviceNameExpressionSource = converter.deviceNameExpressionSource - ? converter.deviceNameExpressionSource as SourceType - : deviceNameExpression ? this.getTypeSourceByValue(deviceNameExpression) : null; - const deviceProfileExpression = converter.deviceProfileExpression - || converter.deviceTypeTopicExpression - || converter.deviceTypeJsonExpression - || 'default'; - const deviceProfileExpressionSource = converter.deviceProfileExpressionSource - ? converter.deviceProfileExpressionSource as SourceType - : deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; - - return deviceNameExpression || deviceProfileExpression ? { - deviceNameExpression, - deviceNameExpressionSource, - deviceProfileExpression, - deviceProfileExpressionSource - } : null; - } - - private static mapRequestToUpgradedVersion(value, key: RequestType): RequestMappingData { - const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; - const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; - const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; - const attributeNameExpression = value.attributeNameExpressionSource || value.attributeNameJsonExpression || null; - const responseTopicQoS = key === RequestType.SERVER_SIDE_RPC ? 1 : null; - const type = key === RequestType.SERVER_SIDE_RPC - ? (value as ServerSideRpc).responseTopicExpression - ? ServerSideRpcType.WithResponse - : ServerSideRpcType.WithoutResponse - : null; - - return { - ...value, - attributeNameExpression, - attributeNameExpressionSource: attributeNameExpression ? this.getTypeSourceByValue(attributeNameExpression) : null, - deviceInfo: value.deviceInfo ? value.deviceInfo : deviceNameExpression ? { - deviceNameExpression, - deviceNameExpressionSource: this.getTypeSourceByValue(deviceNameExpression), - deviceProfileExpression, - deviceProfileExpressionSource - } : null, - responseTopicQoS, - type - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts deleted file mode 100644 index f589dfea97..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts +++ /dev/null @@ -1,143 +0,0 @@ -/// -/// 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 { - Attribute, - AttributesUpdate, - DeviceConnectorMapping, - LegacyAttribute, - LegacyDeviceAttributeUpdate, - LegacyDeviceConnectorMapping, - LegacyRpcMethod, - LegacyServerConfig, - LegacyTimeseries, - OPCBasicConfig_v3_5_2, - OPCUaSourceType, - RpcArgument, - RpcMethod, - ServerConfig, - Timeseries -} from '@home/components/widget/lib/gateway/gateway-widget.models'; - -export class OpcVersionMappingUtil { - - static mapServerToUpgradedVersion(server: LegacyServerConfig): ServerConfig { - const { mapping, disableSubscriptions, pollPeriodInMillis, ...restServer } = server; - return { - ...restServer, - pollPeriodInMillis: pollPeriodInMillis ?? 5000, - enableSubscriptions: !disableSubscriptions, - }; - } - - static mapServerToDowngradedVersion(config: OPCBasicConfig_v3_5_2): LegacyServerConfig { - const { mapping, server } = config; - const { enableSubscriptions, ...restServer } = server ?? {} as ServerConfig; - return { - ...restServer, - mapping: mapping ? this.mapMappingToDowngradedVersion(mapping) : [], - disableSubscriptions: !enableSubscriptions, - }; - } - - static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] { - return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({ - ...legacyMapping, - deviceNodeSource: this.getDeviceNodeSourceByValue(legacyMapping.deviceNodePattern), - deviceInfo: { - deviceNameExpression: legacyMapping.deviceNamePattern, - deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern), - deviceProfileExpression: legacyMapping.deviceTypePattern ?? 'default', - deviceProfileExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceTypePattern ?? 'default'), - }, - attributes: legacyMapping.attributes.map((attribute: LegacyAttribute) => ({ - key: attribute.key, - type: this.getTypeSourceByValue(attribute.path), - value: attribute.path, - })), - attributes_updates: legacyMapping.attributes_updates.map((attributeUpdate: LegacyDeviceAttributeUpdate) => ({ - key: attributeUpdate.attributeOnThingsBoard, - type: this.getTypeSourceByValue(attributeUpdate.attributeOnDevice), - value: attributeUpdate.attributeOnDevice, - })), - timeseries: legacyMapping.timeseries.map((timeseries: LegacyTimeseries) => ({ - key: timeseries.key, - type: this.getTypeSourceByValue(timeseries.path), - value: timeseries.path, - })), - rpc_methods: legacyMapping.rpc_methods.map((rpcMethod: LegacyRpcMethod) => ({ - method: rpcMethod.method, - arguments: rpcMethod.arguments.map(arg => ({ - value: arg, - type: this.getArgumentType(arg), - } as RpcArgument)) - })) - })); - } - - static mapMappingToDowngradedVersion(mapping: DeviceConnectorMapping[]): LegacyDeviceConnectorMapping[] { - return mapping.map((upgradedMapping: DeviceConnectorMapping) => ({ - ...upgradedMapping, - deviceNamePattern: upgradedMapping.deviceInfo.deviceNameExpression, - deviceTypePattern: upgradedMapping.deviceInfo.deviceProfileExpression, - attributes: upgradedMapping.attributes.map((attribute: Attribute) => ({ - key: attribute.key, - path: attribute.value, - })), - attributes_updates: upgradedMapping.attributes_updates.map((attributeUpdate: AttributesUpdate) => ({ - attributeOnThingsBoard: attributeUpdate.key, - attributeOnDevice: attributeUpdate.value, - })), - timeseries: upgradedMapping.timeseries.map((timeseries: Timeseries) => ({ - key: timeseries.key, - path: timeseries.value, - })), - rpc_methods: upgradedMapping.rpc_methods.map((rpcMethod: RpcMethod) => ({ - method: rpcMethod.method, - arguments: rpcMethod.arguments.map((arg: RpcArgument) => arg.value) - })) - })); - } - - private static getTypeSourceByValue(value: string): OPCUaSourceType { - if (value.includes('${')) { - return OPCUaSourceType.IDENTIFIER; - } - if (value.includes(`/`) || value.includes('\\')) { - return OPCUaSourceType.PATH; - } - return OPCUaSourceType.CONST; - } - - private static getDeviceNodeSourceByValue(value: string): OPCUaSourceType { - if (value.includes('${')) { - return OPCUaSourceType.IDENTIFIER; - } else { - return OPCUaSourceType.PATH; - } - } - - private static getArgumentType(arg: unknown): string { - switch (typeof arg) { - case 'boolean': - return 'boolean'; - case 'number': - return Number.isInteger(arg) ? 'integer' : 'float'; - default: - return 'string'; - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html index 7f86897f7e..fb950f6579 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html @@ -18,7 +18,7 @@
-
+
-
-