Commit 5626c30f2f7d64f8838118a2a3db9e83f98ea933

Authored by YevhenBondarenko
2 parents 489c6777 dbc3ef95

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/persisted-rpc

# Conflicts:
#	common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
Showing 57 changed files with 3573 additions and 304 deletions

Too many changes to show.

To preserve performance only 57 of 139 files are displayed.

@@ -223,6 +223,27 @@ @@ -223,6 +223,27 @@
223 "funcBody": null, 223 "funcBody": null,
224 "usePostProcessing": null, 224 "usePostProcessing": null,
225 "postFuncBody": null 225 "postFuncBody": null
  226 + },
  227 + {
  228 + "name": "fw_url",
  229 + "type": "attribute",
  230 + "label": "fw_url",
  231 + "color": "#e91e63",
  232 + "settings": {
  233 + "columnWidth": "0px",
  234 + "useCellStyleFunction": false,
  235 + "cellStyleFunction": "",
  236 + "useCellContentFunction": false,
  237 + "cellContentFunction": "",
  238 + "defaultColumnVisibility": "hidden",
  239 + "columnSelectionToDisplay": "disabled"
  240 + },
  241 + "_hash": 0.4204673738685043,
  242 + "units": null,
  243 + "decimals": null,
  244 + "funcBody": null,
  245 + "usePostProcessing": null,
  246 + "postFuncBody": null
226 } 247 }
227 ] 248 ]
228 } 249 }
@@ -249,23 +270,23 @@ @@ -249,23 +270,23 @@
249 "icon": "edit", 270 "icon": "edit",
250 "type": "customPretty", 271 "type": "customPretty",
251 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", 272 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
252 - "customCss": "", 273 + "customCss": "form {\n min-width: 300px !important;\n}",
253 "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}", 274 "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}",
254 "customResources": [], 275 "customResources": [],
255 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 276 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
256 }, 277 },
257 { 278 {
258 - "name": "Download firware", 279 + "name": "Download firmware",
259 "icon": "file_download", 280 "icon": "file_download",
260 "type": "custom", 281 "type": "custom",
261 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", 282 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
262 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 283 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
263 }, 284 },
264 { 285 {
265 - "name": "Copy checksum", 286 + "name": "Copy checksum/URL",
266 "icon": "content_copy", 287 "icon": "content_copy",
267 "type": "custom", 288 "type": "custom",
268 - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", 289 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
269 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 290 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
270 } 291 }
271 ] 292 ]
@@ -997,6 +1018,27 @@ @@ -997,6 +1018,27 @@
997 "funcBody": null, 1018 "funcBody": null,
998 "usePostProcessing": null, 1019 "usePostProcessing": null,
999 "postFuncBody": null 1020 "postFuncBody": null
  1021 + },
  1022 + {
  1023 + "name": "fw_url",
  1024 + "type": "attribute",
  1025 + "label": "fw_url",
  1026 + "color": "#e91e63",
  1027 + "settings": {
  1028 + "columnWidth": "0px",
  1029 + "useCellStyleFunction": false,
  1030 + "cellStyleFunction": "",
  1031 + "useCellContentFunction": false,
  1032 + "cellContentFunction": "",
  1033 + "defaultColumnVisibility": "hidden",
  1034 + "columnSelectionToDisplay": "disabled"
  1035 + },
  1036 + "_hash": 0.4204673738685043,
  1037 + "units": null,
  1038 + "decimals": null,
  1039 + "funcBody": null,
  1040 + "usePostProcessing": null,
  1041 + "postFuncBody": null
1000 } 1042 }
1001 ] 1043 ]
1002 } 1044 }
@@ -1023,23 +1065,23 @@ @@ -1023,23 +1065,23 @@
1023 "icon": "edit", 1065 "icon": "edit",
1024 "type": "customPretty", 1066 "type": "customPretty",
1025 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", 1067 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1026 - "customCss": "", 1068 + "customCss": "form {\n min-width: 300px !important;\n}",
1027 "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}", 1069 "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}",
1028 "customResources": [], 1070 "customResources": [],
1029 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1071 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1030 }, 1072 },
1031 { 1073 {
1032 - "name": "Download firware", 1074 + "name": "Download firmware",
1033 "icon": "file_download", 1075 "icon": "file_download",
1034 "type": "custom", 1076 "type": "custom",
1035 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", 1077 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1036 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1078 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1037 }, 1079 },
1038 { 1080 {
1039 - "name": "Copy checksum", 1081 + "name": "Copy checksum/URL",
1040 "icon": "content_copy", 1082 "icon": "content_copy",
1041 "type": "custom", 1083 "type": "custom",
1042 - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", 1084 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1043 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1085 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1044 } 1086 }
1045 ] 1087 ]
@@ -1273,6 +1315,27 @@ @@ -1273,6 +1315,27 @@
1273 "funcBody": null, 1315 "funcBody": null,
1274 "usePostProcessing": null, 1316 "usePostProcessing": null,
1275 "postFuncBody": null 1317 "postFuncBody": null
  1318 + },
  1319 + {
  1320 + "name": "fw_url",
  1321 + "type": "attribute",
  1322 + "label": "fw_url",
  1323 + "color": "#e91e63",
  1324 + "settings": {
  1325 + "columnWidth": "0px",
  1326 + "useCellStyleFunction": false,
  1327 + "cellStyleFunction": "",
  1328 + "useCellContentFunction": false,
  1329 + "cellContentFunction": "",
  1330 + "defaultColumnVisibility": "hidden",
  1331 + "columnSelectionToDisplay": "disabled"
  1332 + },
  1333 + "_hash": 0.4204673738685043,
  1334 + "units": null,
  1335 + "decimals": null,
  1336 + "funcBody": null,
  1337 + "usePostProcessing": null,
  1338 + "postFuncBody": null
1276 } 1339 }
1277 ] 1340 ]
1278 } 1341 }
@@ -1299,23 +1362,23 @@ @@ -1299,23 +1362,23 @@
1299 "icon": "edit", 1362 "icon": "edit",
1300 "type": "customPretty", 1363 "type": "customPretty",
1301 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", 1364 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1302 - "customCss": "", 1365 + "customCss": "form {\n min-width: 300px !important;\n}",
1303 "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}", 1366 "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}",
1304 "customResources": [], 1367 "customResources": [],
1305 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1368 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1306 }, 1369 },
1307 { 1370 {
1308 - "name": "Download firware", 1371 + "name": "Download firmware",
1309 "icon": "file_download", 1372 "icon": "file_download",
1310 "type": "custom", 1373 "type": "custom",
1311 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", 1374 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1312 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1375 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1313 }, 1376 },
1314 { 1377 {
1315 - "name": "Copy checksum", 1378 + "name": "Copy checksum/URL",
1316 "icon": "content_copy", 1379 "icon": "content_copy",
1317 "type": "custom", 1380 "type": "custom",
1318 - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", 1381 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1319 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1382 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1320 } 1383 }
1321 ] 1384 ]
@@ -1549,6 +1612,27 @@ @@ -1549,6 +1612,27 @@
1549 "funcBody": null, 1612 "funcBody": null,
1550 "usePostProcessing": null, 1613 "usePostProcessing": null,
1551 "postFuncBody": null 1614 "postFuncBody": null
  1615 + },
  1616 + {
  1617 + "name": "fw_url",
  1618 + "type": "attribute",
  1619 + "label": "fw_url",
  1620 + "color": "#e91e63",
  1621 + "settings": {
  1622 + "columnWidth": "0px",
  1623 + "useCellStyleFunction": false,
  1624 + "cellStyleFunction": "",
  1625 + "useCellContentFunction": false,
  1626 + "cellContentFunction": "",
  1627 + "defaultColumnVisibility": "hidden",
  1628 + "columnSelectionToDisplay": "disabled"
  1629 + },
  1630 + "_hash": 0.4204673738685043,
  1631 + "units": null,
  1632 + "decimals": null,
  1633 + "funcBody": null,
  1634 + "usePostProcessing": null,
  1635 + "postFuncBody": null
1552 } 1636 }
1553 ] 1637 ]
1554 } 1638 }
@@ -1575,23 +1659,23 @@ @@ -1575,23 +1659,23 @@
1575 "icon": "edit", 1659 "icon": "edit",
1576 "type": "customPretty", 1660 "type": "customPretty",
1577 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", 1661 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1578 - "customCss": "", 1662 + "customCss": "form {\n min-width: 300px !important;\n}",
1579 "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}", 1663 "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}",
1580 "customResources": [], 1664 "customResources": [],
1581 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1665 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1582 }, 1666 },
1583 { 1667 {
1584 - "name": "Download firware", 1668 + "name": "Download firmware",
1585 "icon": "file_download", 1669 "icon": "file_download",
1586 "type": "custom", 1670 "type": "custom",
1587 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", 1671 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1588 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1672 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1589 }, 1673 },
1590 { 1674 {
1591 - "name": "Copy checksum", 1675 + "name": "Copy checksum/URL",
1592 "icon": "content_copy", 1676 "icon": "content_copy",
1593 "type": "custom", 1677 "type": "custom",
1594 - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", 1678 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1595 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1679 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1596 } 1680 }
1597 ] 1681 ]
@@ -1825,6 +1909,27 @@ @@ -1825,6 +1909,27 @@
1825 "funcBody": null, 1909 "funcBody": null,
1826 "usePostProcessing": null, 1910 "usePostProcessing": null,
1827 "postFuncBody": null 1911 "postFuncBody": null
  1912 + },
  1913 + {
  1914 + "name": "fw_url",
  1915 + "type": "attribute",
  1916 + "label": "fw_url",
  1917 + "color": "#e91e63",
  1918 + "settings": {
  1919 + "columnWidth": "0px",
  1920 + "useCellStyleFunction": false,
  1921 + "cellStyleFunction": "",
  1922 + "useCellContentFunction": false,
  1923 + "cellContentFunction": "",
  1924 + "defaultColumnVisibility": "hidden",
  1925 + "columnSelectionToDisplay": "disabled"
  1926 + },
  1927 + "_hash": 0.4204673738685043,
  1928 + "units": null,
  1929 + "decimals": null,
  1930 + "funcBody": null,
  1931 + "usePostProcessing": null,
  1932 + "postFuncBody": null
1828 } 1933 }
1829 ] 1934 ]
1830 } 1935 }
@@ -1851,23 +1956,23 @@ @@ -1851,23 +1956,23 @@
1851 "icon": "edit", 1956 "icon": "edit",
1852 "type": "customPretty", 1957 "type": "customPretty",
1853 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", 1958 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1854 - "customCss": "", 1959 + "customCss": "form {\n min-width: 300px !important;\n}",
1855 "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}", 1960 "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}",
1856 "customResources": [], 1961 "customResources": [],
1857 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1962 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1858 }, 1963 },
1859 { 1964 {
1860 - "name": "Download firware", 1965 + "name": "Download firmware",
1861 "icon": "file_download", 1966 "icon": "file_download",
1862 "type": "custom", 1967 "type": "custom",
1863 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", 1968 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1864 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1969 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1865 }, 1970 },
1866 { 1971 {
1867 - "name": "Copy checksum", 1972 + "name": "Copy checksum/URL",
1868 "icon": "content_copy", 1973 "icon": "content_copy",
1869 "type": "custom", 1974 "type": "custom",
1870 - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", 1975 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1871 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1976 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1872 } 1977 }
1873 ] 1978 ]
@@ -1936,7 +2041,7 @@ @@ -1936,7 +2041,7 @@
1936 } 2041 }
1937 }, 2042 },
1938 "device_firmware_history": { 2043 "device_firmware_history": {
1939 - "name": "Device Firmware history", 2044 + "name": "Firmware history: ${entityName}",
1940 "root": false, 2045 "root": false,
1941 "layouts": { 2046 "layouts": {
1942 "main": { 2047 "main": {
@@ -2379,7 +2484,8 @@ @@ -2379,7 +2484,8 @@
2379 "titleColor": "rgba(0,0,0,0.870588)", 2484 "titleColor": "rgba(0,0,0,0.870588)",
2380 "showFilters": true, 2485 "showFilters": true,
2381 "showDashboardLogo": false, 2486 "showDashboardLogo": false,
2382 - "dashboardLogoUrl": null 2487 + "dashboardLogoUrl": null,
  2488 + "showUpdateDashboardImage": false
2383 } 2489 }
2384 }, 2490 },
2385 "name": "Firmware" 2491 "name": "Firmware"
  1 +{
  2 + "title": "Software",
  3 + "image": null,
  4 + "configuration": {
  5 + "description": "",
  6 + "widgets": {
  7 + "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
  8 + "isSystemType": true,
  9 + "bundleAlias": "cards",
  10 + "typeAlias": "entities_table",
  11 + "type": "latest",
  12 + "title": "New widget",
  13 + "image": null,
  14 + "description": null,
  15 + "sizeX": 7.5,
  16 + "sizeY": 6.5,
  17 + "config": {
  18 + "timewindow": {
  19 + "realtime": {
  20 + "interval": 1000,
  21 + "timewindowMs": 86400000
  22 + },
  23 + "aggregation": {
  24 + "type": "NONE",
  25 + "limit": 200
  26 + }
  27 + },
  28 + "showTitle": true,
  29 + "backgroundColor": "rgb(255, 255, 255)",
  30 + "color": "rgba(0, 0, 0, 0.87)",
  31 + "padding": "4px",
  32 + "settings": {
  33 + "enableSearch": true,
  34 + "displayPagination": true,
  35 + "defaultPageSize": 10,
  36 + "defaultSortOrder": "entityLabel",
  37 + "displayEntityName": false,
  38 + "displayEntityType": false,
  39 + "enableSelectColumnDisplay": false,
  40 + "enableStickyHeader": true,
  41 + "enableStickyAction": false,
  42 + "entitiesTitle": "Devices",
  43 + "displayEntityLabel": true,
  44 + "entityLabelColumnTitle": "Device"
  45 + },
  46 + "title": "New Entities table",
  47 + "dropShadow": true,
  48 + "enableFullscreen": true,
  49 + "titleStyle": {
  50 + "fontSize": "16px",
  51 + "fontWeight": 400,
  52 + "padding": "5px 10px 5px 10px"
  53 + },
  54 + "useDashboardTimewindow": false,
  55 + "showLegend": false,
  56 + "datasources": [
  57 + {
  58 + "type": "entity",
  59 + "name": null,
  60 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  61 + "filterId": "8fdb88d0-50ac-2232-fdb7-69c30c16544e",
  62 + "dataKeys": [
  63 + {
  64 + "name": "current_sw_title",
  65 + "type": "timeseries",
  66 + "label": "Current SW title",
  67 + "color": "#2196f3",
  68 + "settings": {
  69 + "columnWidth": "0px",
  70 + "useCellStyleFunction": false,
  71 + "cellStyleFunction": "",
  72 + "useCellContentFunction": false,
  73 + "defaultColumnVisibility": "visible",
  74 + "columnSelectionToDisplay": "enabled"
  75 + },
  76 + "_hash": 0.09545533885166413,
  77 + "units": null,
  78 + "decimals": null,
  79 + "funcBody": null,
  80 + "usePostProcessing": null,
  81 + "postFuncBody": null
  82 + },
  83 + {
  84 + "name": "current_sw_version",
  85 + "type": "timeseries",
  86 + "label": "Current SW version",
  87 + "color": "#4caf50",
  88 + "settings": {
  89 + "columnWidth": "0px",
  90 + "useCellStyleFunction": false,
  91 + "cellStyleFunction": "",
  92 + "useCellContentFunction": false,
  93 + "defaultColumnVisibility": "visible",
  94 + "columnSelectionToDisplay": "enabled"
  95 + },
  96 + "_hash": 0.7206056602328659,
  97 + "units": null,
  98 + "decimals": null,
  99 + "funcBody": null,
  100 + "usePostProcessing": null,
  101 + "postFuncBody": null
  102 + },
  103 + {
  104 + "name": "target_sw_title",
  105 + "type": "timeseries",
  106 + "label": "Target SW title",
  107 + "color": "#ffc107",
  108 + "settings": {
  109 + "columnWidth": "0px",
  110 + "useCellStyleFunction": false,
  111 + "cellStyleFunction": "",
  112 + "useCellContentFunction": false,
  113 + "defaultColumnVisibility": "visible",
  114 + "columnSelectionToDisplay": "enabled"
  115 + },
  116 + "_hash": 0.9934225682766313,
  117 + "units": null,
  118 + "decimals": null,
  119 + "funcBody": null,
  120 + "usePostProcessing": null,
  121 + "postFuncBody": null
  122 + },
  123 + {
  124 + "name": "target_sw_version",
  125 + "type": "timeseries",
  126 + "label": "Target SW version",
  127 + "color": "#607d8b",
  128 + "settings": {
  129 + "columnWidth": "0px",
  130 + "useCellStyleFunction": false,
  131 + "cellStyleFunction": "",
  132 + "useCellContentFunction": false,
  133 + "cellContentFunction": "",
  134 + "defaultColumnVisibility": "visible",
  135 + "columnSelectionToDisplay": "enabled"
  136 + },
  137 + "_hash": 0.5251724416842531,
  138 + "units": null,
  139 + "decimals": null,
  140 + "funcBody": null,
  141 + "usePostProcessing": null,
  142 + "postFuncBody": null
  143 + },
  144 + {
  145 + "name": "target_sw_ts",
  146 + "type": "timeseries",
  147 + "label": "Target SW set time",
  148 + "color": "#e91e63",
  149 + "settings": {
  150 + "columnWidth": "0px",
  151 + "useCellStyleFunction": false,
  152 + "cellStyleFunction": "",
  153 + "useCellContentFunction": true,
  154 + "defaultColumnVisibility": "visible",
  155 + "columnSelectionToDisplay": "enabled",
  156 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  157 + },
  158 + "_hash": 0.31823244858578237,
  159 + "units": null,
  160 + "decimals": null,
  161 + "funcBody": null,
  162 + "usePostProcessing": null,
  163 + "postFuncBody": null
  164 + },
  165 + {
  166 + "name": "sw_state",
  167 + "type": "timeseries",
  168 + "label": "Progress",
  169 + "color": "#9c27b0",
  170 + "settings": {
  171 + "columnWidth": "30%",
  172 + "useCellStyleFunction": true,
  173 + "useCellContentFunction": true,
  174 + "defaultColumnVisibility": "visible",
  175 + "columnSelectionToDisplay": "enabled",
  176 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  177 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  178 + },
  179 + "_hash": 0.8174211757846257,
  180 + "units": null,
  181 + "decimals": null,
  182 + "funcBody": null,
  183 + "usePostProcessing": null,
  184 + "postFuncBody": null
  185 + },
  186 + {
  187 + "name": "sw_state",
  188 + "type": "timeseries",
  189 + "label": "Status",
  190 + "color": "#f44336",
  191 + "settings": {
  192 + "columnWidth": "130px",
  193 + "useCellStyleFunction": true,
  194 + "useCellContentFunction": true,
  195 + "defaultColumnVisibility": "visible",
  196 + "columnSelectionToDisplay": "enabled",
  197 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  198 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  199 + },
  200 + "_hash": 0.7764426948615217,
  201 + "units": null,
  202 + "decimals": null,
  203 + "funcBody": null,
  204 + "usePostProcessing": null,
  205 + "postFuncBody": null
  206 + },
  207 + {
  208 + "name": "sw_checksum",
  209 + "type": "attribute",
  210 + "label": "sw_checksum",
  211 + "color": "#3f51b5",
  212 + "settings": {
  213 + "columnWidth": "0px",
  214 + "useCellStyleFunction": false,
  215 + "cellStyleFunction": "",
  216 + "useCellContentFunction": false,
  217 + "defaultColumnVisibility": "hidden",
  218 + "columnSelectionToDisplay": "disabled"
  219 + },
  220 + "_hash": 0.5594087842471693,
  221 + "units": null,
  222 + "decimals": null,
  223 + "funcBody": null,
  224 + "usePostProcessing": null,
  225 + "postFuncBody": null
  226 + },
  227 + {
  228 + "name": "sw_url",
  229 + "type": "attribute",
  230 + "label": "sw_url",
  231 + "color": "#e91e63",
  232 + "settings": {
  233 + "columnWidth": "0px",
  234 + "useCellStyleFunction": false,
  235 + "cellStyleFunction": "",
  236 + "useCellContentFunction": false,
  237 + "cellContentFunction": "",
  238 + "defaultColumnVisibility": "hidden",
  239 + "columnSelectionToDisplay": "disabled"
  240 + },
  241 + "_hash": 0.3355829384124256,
  242 + "units": null,
  243 + "decimals": null,
  244 + "funcBody": null,
  245 + "usePostProcessing": null,
  246 + "postFuncBody": null
  247 + }
  248 + ]
  249 + }
  250 + ],
  251 + "actions": {
  252 + "actionCellButton": [
  253 + {
  254 + "name": "History software update",
  255 + "icon": "history",
  256 + "type": "openDashboardState",
  257 + "targetDashboardStateId": "device_software_history",
  258 + "setEntityId": true,
  259 + "stateEntityParamName": null,
  260 + "openInSeparateDialog": false,
  261 + "dialogTitle": "",
  262 + "dialogHideDashboardToolbar": true,
  263 + "dialogWidth": null,
  264 + "dialogHeight": null,
  265 + "openRightLayout": false,
  266 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  267 + },
  268 + {
  269 + "name": "Edit software",
  270 + "icon": "edit",
  271 + "type": "customPretty",
  272 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  273 + "customCss": "form {\n min-width: 300px !important;\n}",
  274 + "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}",
  275 + "customResources": [],
  276 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  277 + },
  278 + {
  279 + "name": "Download software",
  280 + "icon": "file_download",
  281 + "type": "custom",
  282 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  283 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  284 + },
  285 + {
  286 + "name": "Copy checksum/URL",
  287 + "icon": "content_copy",
  288 + "type": "custom",
  289 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  290 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  291 + }
  292 + ]
  293 + },
  294 + "showTitleIcon": false,
  295 + "iconColor": "rgba(0, 0, 0, 0.87)",
  296 + "iconSize": "24px",
  297 + "titleTooltip": "",
  298 + "widgetStyle": {}
  299 + },
  300 + "row": 0,
  301 + "col": 0,
  302 + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016"
  303 + },
  304 + "100b756c-0082-6505-3ae1-3603e6deea48": {
  305 + "isSystemType": true,
  306 + "bundleAlias": "cards",
  307 + "typeAlias": "timeseries_table",
  308 + "type": "timeseries",
  309 + "title": "New widget",
  310 + "image": null,
  311 + "description": null,
  312 + "sizeX": 8,
  313 + "sizeY": 6.5,
  314 + "config": {
  315 + "datasources": [
  316 + {
  317 + "type": "entity",
  318 + "name": null,
  319 + "entityAliasId": "19f41c21-d9af-e666-8f50-e1748778f955",
  320 + "filterId": null,
  321 + "dataKeys": [
  322 + {
  323 + "name": "current_sw_title",
  324 + "type": "timeseries",
  325 + "label": "Current software title",
  326 + "color": "#2196f3",
  327 + "settings": {
  328 + "useCellStyleFunction": false,
  329 + "cellStyleFunction": "",
  330 + "useCellContentFunction": false,
  331 + "cellContentFunction": ""
  332 + },
  333 + "_hash": 0.5978079905579401,
  334 + "units": null,
  335 + "decimals": null,
  336 + "funcBody": null,
  337 + "usePostProcessing": null,
  338 + "postFuncBody": null
  339 + },
  340 + {
  341 + "name": "current_sw_version",
  342 + "type": "timeseries",
  343 + "label": "Current software version",
  344 + "color": "#4caf50",
  345 + "settings": {
  346 + "useCellStyleFunction": false,
  347 + "cellStyleFunction": "",
  348 + "useCellContentFunction": false,
  349 + "cellContentFunction": ""
  350 + },
  351 + "_hash": 0.027392025058568192,
  352 + "units": null,
  353 + "decimals": null,
  354 + "funcBody": null,
  355 + "usePostProcessing": null,
  356 + "postFuncBody": null
  357 + },
  358 + {
  359 + "name": "target_sw_title",
  360 + "type": "timeseries",
  361 + "label": "Target software title",
  362 + "color": "#f44336",
  363 + "settings": {
  364 + "useCellStyleFunction": false,
  365 + "cellStyleFunction": "",
  366 + "useCellContentFunction": false,
  367 + "cellContentFunction": ""
  368 + },
  369 + "_hash": 0.9496350796287059,
  370 + "units": null,
  371 + "decimals": null,
  372 + "funcBody": null,
  373 + "usePostProcessing": null,
  374 + "postFuncBody": null
  375 + },
  376 + {
  377 + "name": "target_sw_version",
  378 + "type": "timeseries",
  379 + "label": "Target software version",
  380 + "color": "#ffc107",
  381 + "settings": {
  382 + "useCellStyleFunction": false,
  383 + "cellStyleFunction": "",
  384 + "useCellContentFunction": false,
  385 + "cellContentFunction": ""
  386 + },
  387 + "_hash": 0.6734152252264187,
  388 + "units": null,
  389 + "decimals": null,
  390 + "funcBody": null,
  391 + "usePostProcessing": null,
  392 + "postFuncBody": null
  393 + },
  394 + {
  395 + "name": "sw_state",
  396 + "type": "timeseries",
  397 + "label": "Status",
  398 + "color": "#607d8b",
  399 + "settings": {
  400 + "useCellStyleFunction": false,
  401 + "cellStyleFunction": "",
  402 + "useCellContentFunction": false,
  403 + "cellContentFunction": ""
  404 + },
  405 + "_hash": 0.2983399718643074,
  406 + "units": null,
  407 + "decimals": null,
  408 + "funcBody": null,
  409 + "usePostProcessing": true,
  410 + "postFuncBody": "function capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\nif (value !== '') {\n return capitalize(value);\n}\nreturn value;"
  411 + }
  412 + ]
  413 + }
  414 + ],
  415 + "timewindow": {
  416 + "hideInterval": false,
  417 + "hideAggregation": false,
  418 + "hideAggInterval": false,
  419 + "hideTimezone": false,
  420 + "selectedTab": 0,
  421 + "realtime": {
  422 + "realtimeType": 0,
  423 + "timewindowMs": 2592000000,
  424 + "quickInterval": "CURRENT_DAY",
  425 + "interval": 1000
  426 + },
  427 + "aggregation": {
  428 + "type": "NONE",
  429 + "limit": 200
  430 + }
  431 + },
  432 + "showTitle": false,
  433 + "backgroundColor": "rgb(255, 255, 255)",
  434 + "color": "rgba(0, 0, 0, 0.87)",
  435 + "padding": "8px",
  436 + "settings": {
  437 + "showTimestamp": true,
  438 + "displayPagination": true,
  439 + "defaultPageSize": 10,
  440 + "enableSearch": true,
  441 + "enableStickyHeader": true,
  442 + "enableStickyAction": true
  443 + },
  444 + "title": "Software history",
  445 + "dropShadow": false,
  446 + "enableFullscreen": false,
  447 + "titleStyle": {
  448 + "fontSize": "16px",
  449 + "fontWeight": 400,
  450 + "padding": "5px 10px 5px 10px"
  451 + },
  452 + "useDashboardTimewindow": false,
  453 + "showLegend": false,
  454 + "widgetStyle": {},
  455 + "actions": {},
  456 + "showTitleIcon": false,
  457 + "iconColor": "rgba(0, 0, 0, 0.87)",
  458 + "iconSize": "24px",
  459 + "displayTimewindow": true,
  460 + "titleTooltip": ""
  461 + },
  462 + "row": 0,
  463 + "col": 0,
  464 + "id": "100b756c-0082-6505-3ae1-3603e6deea48"
  465 + },
  466 + "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
  467 + "isSystemType": true,
  468 + "bundleAlias": "cards",
  469 + "typeAlias": "html_value_card",
  470 + "type": "latest",
  471 + "title": "New widget",
  472 + "sizeX": 8,
  473 + "sizeY": 3,
  474 + "config": {
  475 + "datasources": [
  476 + {
  477 + "type": "entityCount",
  478 + "name": "",
  479 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  480 + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  481 + "dataKeys": [
  482 + {
  483 + "name": "count",
  484 + "type": "count",
  485 + "label": "waitingDevicesNumber",
  486 + "color": "#4caf50",
  487 + "settings": {},
  488 + "_hash": 0.7404827038869322,
  489 + "units": null,
  490 + "decimals": null,
  491 + "funcBody": null,
  492 + "usePostProcessing": null,
  493 + "postFuncBody": null
  494 + }
  495 + ]
  496 + }
  497 + ],
  498 + "timewindow": {
  499 + "realtime": {
  500 + "timewindowMs": 60000
  501 + }
  502 + },
  503 + "showTitle": false,
  504 + "backgroundColor": "#fff",
  505 + "color": "rgba(0, 0, 0, 0.87)",
  506 + "padding": "0px",
  507 + "settings": {
  508 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"24\" height=\"40\" viewBox=\"0 0 24 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M0 0V12H0.0200005L0 12.02L8 20L0 28L0.0200005 28.02H0V40H24V28.02H23.98L24 28L16 20L24 12.02L23.98 12H24V0H0ZM20 29V36H4V29L12 21L20 29ZM12 19L4 11V4H20V11L12 19Z\" fill=\"black\"/>\n</svg>\n'>\n <div class='value' id=\"activeDevices\">\n ${waitingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Waiting\n </div>\n </div>\n</div>",
  509 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  510 + },
  511 + "title": "New HTML Value Card",
  512 + "dropShadow": true,
  513 + "enableFullscreen": false,
  514 + "widgetStyle": {},
  515 + "titleStyle": {
  516 + "fontSize": "16px",
  517 + "fontWeight": 400
  518 + },
  519 + "useDashboardTimewindow": true,
  520 + "showLegend": false,
  521 + "actions": {
  522 + "elementClick": [
  523 + {
  524 + "name": "activeDevices",
  525 + "icon": "more_horiz",
  526 + "type": "openDashboardState",
  527 + "targetDashboardStateId": "device_waiting",
  528 + "setEntityId": false,
  529 + "stateEntityParamName": null,
  530 + "openInSeparateDialog": false,
  531 + "dialogTitle": "",
  532 + "dialogHideDashboardToolbar": true,
  533 + "dialogWidth": null,
  534 + "dialogHeight": null,
  535 + "openRightLayout": false,
  536 + "id": "4d9a77a2-f0a5-690c-a83b-b0e940be788c"
  537 + }
  538 + ]
  539 + },
  540 + "showTitleIcon": false,
  541 + "titleIcon": null,
  542 + "iconColor": "rgba(0, 0, 0, 0.87)",
  543 + "iconSize": "24px",
  544 + "titleTooltip": "",
  545 + "enableDataExport": false,
  546 + "displayTimewindow": true
  547 + },
  548 + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6"
  549 + },
  550 + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
  551 + "isSystemType": true,
  552 + "bundleAlias": "cards",
  553 + "typeAlias": "html_value_card",
  554 + "type": "latest",
  555 + "title": "New widget",
  556 + "sizeX": 8,
  557 + "sizeY": 3,
  558 + "config": {
  559 + "datasources": [
  560 + {
  561 + "type": "entityCount",
  562 + "name": "",
  563 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  564 + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  565 + "dataKeys": [
  566 + {
  567 + "name": "count",
  568 + "type": "count",
  569 + "label": "updatingDevicesNumber",
  570 + "color": "#4caf50",
  571 + "settings": {},
  572 + "_hash": 0.7404827038869322,
  573 + "units": null,
  574 + "decimals": null,
  575 + "funcBody": null,
  576 + "usePostProcessing": null,
  577 + "postFuncBody": null
  578 + }
  579 + ]
  580 + }
  581 + ],
  582 + "timewindow": {
  583 + "realtime": {
  584 + "timewindowMs": 60000
  585 + }
  586 + },
  587 + "showTitle": false,
  588 + "backgroundColor": "#fff",
  589 + "color": "rgba(0, 0, 0, 0.87)",
  590 + "padding": "0px",
  591 + "settings": {
  592 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"36\" height=\"36\" viewBox=\"0 0 36 36\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M36 14.24H22.44L27.92 8.6C22.46 3.2 13.62 3 8.16001 8.4C2.70001 13.82 2.70001 22.56 8.16001 27.98C13.62 33.4 22.46 33.4 27.92 27.98C30.64 25.3 32 22.16 32 18.2H36C36 22.16 34.24 27.3 30.72 30.78C23.7 37.74 12.3 37.74 5.28001 30.78C-1.71999 23.84 -1.77999 12.56 5.24001 5.62C12.26 -1.32 23.52 -1.32 30.54 5.62L36 0V14.24ZM19 10V18.5L26 22.66L24.56 25.08L16 20V10H19Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updating\n </div>\n </div>\n</div>",
  593 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  594 + },
  595 + "title": "New HTML Value Card",
  596 + "dropShadow": true,
  597 + "enableFullscreen": false,
  598 + "widgetStyle": {},
  599 + "titleStyle": {
  600 + "fontSize": "16px",
  601 + "fontWeight": 400
  602 + },
  603 + "useDashboardTimewindow": true,
  604 + "showLegend": false,
  605 + "actions": {
  606 + "elementClick": [
  607 + {
  608 + "name": "activeDevices",
  609 + "icon": "more_horiz",
  610 + "type": "openDashboardState",
  611 + "targetDashboardStateId": "device_updating",
  612 + "setEntityId": false,
  613 + "stateEntityParamName": null,
  614 + "openInSeparateDialog": false,
  615 + "dialogTitle": "",
  616 + "dialogHideDashboardToolbar": true,
  617 + "dialogWidth": null,
  618 + "dialogHeight": null,
  619 + "openRightLayout": false,
  620 + "id": "57d39904-2350-b29b-78ed-56b8268814cb"
  621 + }
  622 + ]
  623 + },
  624 + "showTitleIcon": false,
  625 + "titleIcon": null,
  626 + "iconColor": "rgba(0, 0, 0, 0.87)",
  627 + "iconSize": "24px",
  628 + "titleTooltip": "",
  629 + "enableDataExport": false,
  630 + "displayTimewindow": true
  631 + },
  632 + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6"
  633 + },
  634 + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
  635 + "isSystemType": true,
  636 + "bundleAlias": "cards",
  637 + "typeAlias": "html_value_card",
  638 + "type": "latest",
  639 + "title": "New widget",
  640 + "sizeX": 8,
  641 + "sizeY": 3,
  642 + "config": {
  643 + "datasources": [
  644 + {
  645 + "type": "entityCount",
  646 + "name": "",
  647 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  648 + "filterId": "6044e198-df64-cd76-f339-696f220c4943",
  649 + "dataKeys": [
  650 + {
  651 + "name": "count",
  652 + "type": "count",
  653 + "label": "updatedDevicesNumber",
  654 + "color": "#4caf50",
  655 + "settings": {},
  656 + "_hash": 0.7404827038869322,
  657 + "units": null,
  658 + "decimals": null,
  659 + "funcBody": null,
  660 + "usePostProcessing": null,
  661 + "postFuncBody": null
  662 + }
  663 + ]
  664 + }
  665 + ],
  666 + "timewindow": {
  667 + "realtime": {
  668 + "timewindowMs": 60000
  669 + }
  670 + },
  671 + "showTitle": false,
  672 + "backgroundColor": "#fff",
  673 + "color": "rgba(0, 0, 0, 0.87)",
  674 + "padding": "0px",
  675 + "settings": {
  676 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"34\" height=\"32\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/>\n<path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatedDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updated\n </div>\n </div>\n</div>",
  677 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  678 + },
  679 + "title": "New HTML Value Card",
  680 + "dropShadow": true,
  681 + "enableFullscreen": false,
  682 + "widgetStyle": {},
  683 + "titleStyle": {
  684 + "fontSize": "16px",
  685 + "fontWeight": 400
  686 + },
  687 + "useDashboardTimewindow": true,
  688 + "showLegend": false,
  689 + "actions": {
  690 + "elementClick": [
  691 + {
  692 + "name": "activeDevices",
  693 + "icon": "more_horiz",
  694 + "type": "openDashboardState",
  695 + "targetDashboardStateId": "device_updated",
  696 + "setEntityId": false,
  697 + "stateEntityParamName": null,
  698 + "openInSeparateDialog": false,
  699 + "dialogTitle": "",
  700 + "dialogHideDashboardToolbar": true,
  701 + "dialogWidth": null,
  702 + "dialogHeight": null,
  703 + "openRightLayout": false,
  704 + "id": "d787c212-8c56-34f0-349a-5aae2ffd1eae"
  705 + }
  706 + ]
  707 + },
  708 + "showTitleIcon": false,
  709 + "titleIcon": null,
  710 + "iconColor": "rgba(0, 0, 0, 0.87)",
  711 + "iconSize": "24px",
  712 + "titleTooltip": "",
  713 + "enableDataExport": false,
  714 + "displayTimewindow": true
  715 + },
  716 + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81"
  717 + },
  718 + "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
  719 + "isSystemType": true,
  720 + "bundleAlias": "cards",
  721 + "typeAlias": "html_value_card",
  722 + "type": "latest",
  723 + "title": "New widget",
  724 + "sizeX": 8,
  725 + "sizeY": 3,
  726 + "config": {
  727 + "datasources": [
  728 + {
  729 + "type": "entityCount",
  730 + "name": "",
  731 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  732 + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  733 + "dataKeys": [
  734 + {
  735 + "name": "count",
  736 + "type": "count",
  737 + "label": "updatingDevicesNumber",
  738 + "color": "#4caf50",
  739 + "settings": {},
  740 + "_hash": 0.7404827038869322,
  741 + "units": null,
  742 + "decimals": null,
  743 + "funcBody": null,
  744 + "usePostProcessing": null,
  745 + "postFuncBody": null
  746 + }
  747 + ]
  748 + }
  749 + ],
  750 + "timewindow": {
  751 + "realtime": {
  752 + "timewindowMs": 60000
  753 + }
  754 + },
  755 + "showTitle": false,
  756 + "backgroundColor": "#fff",
  757 + "color": "rgba(0, 0, 0, 0.87)",
  758 + "padding": "0px",
  759 + "settings": {
  760 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <div class=\"container-svg\" id=\"activeDevices\">\n <svg viewBox=\"0 0 24 24\" id=\"activeDevices\">\n <path id=\"activeDevices\" fill=\"currentColor\" d=\"M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z\" />\n </svg>\n </div>\n <div class='value error_software_failed_count' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <script type=\"text/javascript\">\n function init() {\n var counter = $('.error_software_failed_count');\n var value = +counter.text();\n if(value) {\n counter.css('color', '#D93025');\n }\n };\n init();\n </script>\n <div class='description' id=\"activeDevices\">\n Device Failed\n </div>\n </div>\n</div>",
  761 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .container-svg {\n height: 40px;\n width: 40px;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .container-svg {\n height: 28px;\n width: 28px;\n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  762 + },
  763 + "title": "New HTML Value Card",
  764 + "dropShadow": true,
  765 + "enableFullscreen": false,
  766 + "widgetStyle": {},
  767 + "titleStyle": {
  768 + "fontSize": "16px",
  769 + "fontWeight": 400
  770 + },
  771 + "useDashboardTimewindow": true,
  772 + "showLegend": false,
  773 + "actions": {
  774 + "elementClick": [
  775 + {
  776 + "name": "activeDevices",
  777 + "icon": "more_horiz",
  778 + "type": "openDashboardState",
  779 + "targetDashboardStateId": "device_error",
  780 + "setEntityId": false,
  781 + "stateEntityParamName": null,
  782 + "openInSeparateDialog": false,
  783 + "dialogTitle": "",
  784 + "dialogHideDashboardToolbar": true,
  785 + "dialogWidth": null,
  786 + "dialogHeight": null,
  787 + "openRightLayout": false,
  788 + "id": "0b3d2887-9929-84d5-3795-0763dca15cba"
  789 + }
  790 + ]
  791 + },
  792 + "showTitleIcon": false,
  793 + "titleIcon": null,
  794 + "iconColor": "rgba(0, 0, 0, 0.87)",
  795 + "iconSize": "24px",
  796 + "titleTooltip": "",
  797 + "enableDataExport": false,
  798 + "displayTimewindow": true
  799 + },
  800 + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8"
  801 + },
  802 + "21be08bb-ec90-f760-ad6f-e7678f12c401": {
  803 + "isSystemType": true,
  804 + "bundleAlias": "cards",
  805 + "typeAlias": "entities_table",
  806 + "type": "latest",
  807 + "title": "New widget",
  808 + "image": null,
  809 + "description": null,
  810 + "sizeX": 7.5,
  811 + "sizeY": 6.5,
  812 + "config": {
  813 + "timewindow": {
  814 + "realtime": {
  815 + "interval": 1000,
  816 + "timewindowMs": 86400000
  817 + },
  818 + "aggregation": {
  819 + "type": "NONE",
  820 + "limit": 200
  821 + }
  822 + },
  823 + "showTitle": true,
  824 + "backgroundColor": "rgb(255, 255, 255)",
  825 + "color": "rgba(0, 0, 0, 0.87)",
  826 + "padding": "4px",
  827 + "settings": {
  828 + "enableSearch": true,
  829 + "displayPagination": true,
  830 + "defaultPageSize": 10,
  831 + "defaultSortOrder": "entityLabel",
  832 + "displayEntityName": false,
  833 + "displayEntityType": false,
  834 + "enableSelectColumnDisplay": false,
  835 + "enableStickyHeader": true,
  836 + "enableStickyAction": true,
  837 + "entitiesTitle": "Devices",
  838 + "displayEntityLabel": true,
  839 + "entityLabelColumnTitle": "Device"
  840 + },
  841 + "title": "New Entities table",
  842 + "dropShadow": true,
  843 + "enableFullscreen": true,
  844 + "titleStyle": {
  845 + "fontSize": "16px",
  846 + "fontWeight": 400,
  847 + "padding": "5px 10px 5px 10px"
  848 + },
  849 + "useDashboardTimewindow": false,
  850 + "showLegend": false,
  851 + "datasources": [
  852 + {
  853 + "type": "entity",
  854 + "name": null,
  855 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  856 + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  857 + "dataKeys": [
  858 + {
  859 + "name": "current_sw_title",
  860 + "type": "timeseries",
  861 + "label": "Current SW title",
  862 + "color": "#2196f3",
  863 + "settings": {
  864 + "columnWidth": "0px",
  865 + "useCellStyleFunction": false,
  866 + "cellStyleFunction": "",
  867 + "useCellContentFunction": false,
  868 + "defaultColumnVisibility": "visible",
  869 + "columnSelectionToDisplay": "enabled"
  870 + },
  871 + "_hash": 0.09545533885166413,
  872 + "units": null,
  873 + "decimals": null,
  874 + "funcBody": null,
  875 + "usePostProcessing": null,
  876 + "postFuncBody": null
  877 + },
  878 + {
  879 + "name": "current_sw_version",
  880 + "type": "timeseries",
  881 + "label": "Current SW version",
  882 + "color": "#4caf50",
  883 + "settings": {
  884 + "columnWidth": "0px",
  885 + "useCellStyleFunction": false,
  886 + "cellStyleFunction": "",
  887 + "useCellContentFunction": false,
  888 + "defaultColumnVisibility": "visible",
  889 + "columnSelectionToDisplay": "enabled"
  890 + },
  891 + "_hash": 0.7206056602328659,
  892 + "units": null,
  893 + "decimals": null,
  894 + "funcBody": null,
  895 + "usePostProcessing": null,
  896 + "postFuncBody": null
  897 + },
  898 + {
  899 + "name": "target_sw_title",
  900 + "type": "timeseries",
  901 + "label": "Target SW title",
  902 + "color": "#ffc107",
  903 + "settings": {
  904 + "columnWidth": "0px",
  905 + "useCellStyleFunction": false,
  906 + "cellStyleFunction": "",
  907 + "useCellContentFunction": false,
  908 + "defaultColumnVisibility": "visible",
  909 + "columnSelectionToDisplay": "enabled"
  910 + },
  911 + "_hash": 0.9934225682766313,
  912 + "units": null,
  913 + "decimals": null,
  914 + "funcBody": null,
  915 + "usePostProcessing": null,
  916 + "postFuncBody": null
  917 + },
  918 + {
  919 + "name": "target_sw_version",
  920 + "type": "timeseries",
  921 + "label": "Target SW version",
  922 + "color": "#607d8b",
  923 + "settings": {
  924 + "columnWidth": "0px",
  925 + "useCellStyleFunction": false,
  926 + "cellStyleFunction": "",
  927 + "useCellContentFunction": false,
  928 + "cellContentFunction": "",
  929 + "defaultColumnVisibility": "visible",
  930 + "columnSelectionToDisplay": "enabled"
  931 + },
  932 + "_hash": 0.5251724416842531,
  933 + "units": null,
  934 + "decimals": null,
  935 + "funcBody": null,
  936 + "usePostProcessing": null,
  937 + "postFuncBody": null
  938 + },
  939 + {
  940 + "name": "target_sw_ts",
  941 + "type": "timeseries",
  942 + "label": "Target SW set time",
  943 + "color": "#e91e63",
  944 + "settings": {
  945 + "columnWidth": "0px",
  946 + "useCellStyleFunction": false,
  947 + "cellStyleFunction": "",
  948 + "useCellContentFunction": true,
  949 + "defaultColumnVisibility": "visible",
  950 + "columnSelectionToDisplay": "enabled",
  951 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  952 + },
  953 + "_hash": 0.31823244858578237,
  954 + "units": null,
  955 + "decimals": null,
  956 + "funcBody": null,
  957 + "usePostProcessing": null,
  958 + "postFuncBody": null
  959 + },
  960 + {
  961 + "name": "sw_state",
  962 + "type": "timeseries",
  963 + "label": "Progress",
  964 + "color": "#9c27b0",
  965 + "settings": {
  966 + "columnWidth": "30%",
  967 + "useCellStyleFunction": true,
  968 + "useCellContentFunction": true,
  969 + "defaultColumnVisibility": "visible",
  970 + "columnSelectionToDisplay": "enabled",
  971 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  972 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  973 + },
  974 + "_hash": 0.8174211757846257,
  975 + "units": null,
  976 + "decimals": null,
  977 + "funcBody": null,
  978 + "usePostProcessing": null,
  979 + "postFuncBody": null
  980 + },
  981 + {
  982 + "name": "sw_state",
  983 + "type": "timeseries",
  984 + "label": "Status",
  985 + "color": "#f44336",
  986 + "settings": {
  987 + "columnWidth": "130px",
  988 + "useCellStyleFunction": true,
  989 + "useCellContentFunction": true,
  990 + "defaultColumnVisibility": "visible",
  991 + "columnSelectionToDisplay": "enabled",
  992 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  993 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  994 + },
  995 + "_hash": 0.7764426948615217,
  996 + "units": null,
  997 + "decimals": null,
  998 + "funcBody": null,
  999 + "usePostProcessing": null,
  1000 + "postFuncBody": null
  1001 + },
  1002 + {
  1003 + "name": "sw_checksum",
  1004 + "type": "attribute",
  1005 + "label": "sw_checksum",
  1006 + "color": "#3f51b5",
  1007 + "settings": {
  1008 + "columnWidth": "0px",
  1009 + "useCellStyleFunction": false,
  1010 + "cellStyleFunction": "",
  1011 + "useCellContentFunction": false,
  1012 + "defaultColumnVisibility": "hidden",
  1013 + "columnSelectionToDisplay": "disabled"
  1014 + },
  1015 + "_hash": 0.5594087842471693,
  1016 + "units": null,
  1017 + "decimals": null,
  1018 + "funcBody": null,
  1019 + "usePostProcessing": null,
  1020 + "postFuncBody": null
  1021 + },
  1022 + {
  1023 + "name": "sw_url",
  1024 + "type": "attribute",
  1025 + "label": "sw_url",
  1026 + "color": "#e91e63",
  1027 + "settings": {
  1028 + "columnWidth": "0px",
  1029 + "useCellStyleFunction": false,
  1030 + "cellStyleFunction": "",
  1031 + "useCellContentFunction": false,
  1032 + "cellContentFunction": "",
  1033 + "defaultColumnVisibility": "hidden",
  1034 + "columnSelectionToDisplay": "disabled"
  1035 + },
  1036 + "_hash": 0.3355829384124256,
  1037 + "units": null,
  1038 + "decimals": null,
  1039 + "funcBody": null,
  1040 + "usePostProcessing": null,
  1041 + "postFuncBody": null
  1042 + }
  1043 + ]
  1044 + }
  1045 + ],
  1046 + "actions": {
  1047 + "actionCellButton": [
  1048 + {
  1049 + "name": "History software update",
  1050 + "icon": "history",
  1051 + "type": "openDashboardState",
  1052 + "targetDashboardStateId": "device_software_history",
  1053 + "setEntityId": true,
  1054 + "stateEntityParamName": null,
  1055 + "openInSeparateDialog": false,
  1056 + "dialogTitle": "",
  1057 + "dialogHideDashboardToolbar": true,
  1058 + "dialogWidth": null,
  1059 + "dialogHeight": null,
  1060 + "openRightLayout": false,
  1061 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1062 + },
  1063 + {
  1064 + "name": "Edit software",
  1065 + "icon": "edit",
  1066 + "type": "customPretty",
  1067 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1068 + "customCss": "form {\n min-width: 300px !important;\n}",
  1069 + "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}",
  1070 + "customResources": [],
  1071 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1072 + },
  1073 + {
  1074 + "name": "Download software",
  1075 + "icon": "file_download",
  1076 + "type": "custom",
  1077 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1078 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1079 + },
  1080 + {
  1081 + "name": "Copy checksum/URL",
  1082 + "icon": "content_copy",
  1083 + "type": "custom",
  1084 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1085 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1086 + }
  1087 + ]
  1088 + },
  1089 + "showTitleIcon": false,
  1090 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1091 + "iconSize": "24px",
  1092 + "titleTooltip": "",
  1093 + "widgetStyle": {}
  1094 + },
  1095 + "row": 0,
  1096 + "col": 0,
  1097 + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401"
  1098 + },
  1099 + "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
  1100 + "isSystemType": true,
  1101 + "bundleAlias": "cards",
  1102 + "typeAlias": "entities_table",
  1103 + "type": "latest",
  1104 + "title": "New widget",
  1105 + "image": null,
  1106 + "description": null,
  1107 + "sizeX": 7.5,
  1108 + "sizeY": 6.5,
  1109 + "config": {
  1110 + "timewindow": {
  1111 + "realtime": {
  1112 + "interval": 1000,
  1113 + "timewindowMs": 86400000
  1114 + },
  1115 + "aggregation": {
  1116 + "type": "NONE",
  1117 + "limit": 200
  1118 + }
  1119 + },
  1120 + "showTitle": true,
  1121 + "backgroundColor": "rgb(255, 255, 255)",
  1122 + "color": "rgba(0, 0, 0, 0.87)",
  1123 + "padding": "4px",
  1124 + "settings": {
  1125 + "enableSearch": true,
  1126 + "displayPagination": true,
  1127 + "defaultPageSize": 10,
  1128 + "defaultSortOrder": "entityLabel",
  1129 + "displayEntityName": false,
  1130 + "displayEntityType": false,
  1131 + "enableSelectColumnDisplay": false,
  1132 + "enableStickyHeader": true,
  1133 + "enableStickyAction": true,
  1134 + "entitiesTitle": "Devices",
  1135 + "displayEntityLabel": true,
  1136 + "entityLabelColumnTitle": "Device"
  1137 + },
  1138 + "title": "New Entities table",
  1139 + "dropShadow": true,
  1140 + "enableFullscreen": true,
  1141 + "titleStyle": {
  1142 + "fontSize": "16px",
  1143 + "fontWeight": 400,
  1144 + "padding": "5px 10px 5px 10px"
  1145 + },
  1146 + "useDashboardTimewindow": false,
  1147 + "showLegend": false,
  1148 + "datasources": [
  1149 + {
  1150 + "type": "entity",
  1151 + "name": null,
  1152 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1153 + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  1154 + "dataKeys": [
  1155 + {
  1156 + "name": "current_sw_title",
  1157 + "type": "timeseries",
  1158 + "label": "Current SW title",
  1159 + "color": "#2196f3",
  1160 + "settings": {
  1161 + "columnWidth": "0px",
  1162 + "useCellStyleFunction": false,
  1163 + "cellStyleFunction": "",
  1164 + "useCellContentFunction": false,
  1165 + "defaultColumnVisibility": "visible",
  1166 + "columnSelectionToDisplay": "enabled"
  1167 + },
  1168 + "_hash": 0.09545533885166413,
  1169 + "units": null,
  1170 + "decimals": null,
  1171 + "funcBody": null,
  1172 + "usePostProcessing": null,
  1173 + "postFuncBody": null
  1174 + },
  1175 + {
  1176 + "name": "current_sw_version",
  1177 + "type": "timeseries",
  1178 + "label": "Current SW version",
  1179 + "color": "#4caf50",
  1180 + "settings": {
  1181 + "columnWidth": "0px",
  1182 + "useCellStyleFunction": false,
  1183 + "cellStyleFunction": "",
  1184 + "useCellContentFunction": false,
  1185 + "defaultColumnVisibility": "visible",
  1186 + "columnSelectionToDisplay": "enabled"
  1187 + },
  1188 + "_hash": 0.7206056602328659,
  1189 + "units": null,
  1190 + "decimals": null,
  1191 + "funcBody": null,
  1192 + "usePostProcessing": null,
  1193 + "postFuncBody": null
  1194 + },
  1195 + {
  1196 + "name": "target_sw_title",
  1197 + "type": "timeseries",
  1198 + "label": "Target SW title",
  1199 + "color": "#ffc107",
  1200 + "settings": {
  1201 + "columnWidth": "0px",
  1202 + "useCellStyleFunction": false,
  1203 + "cellStyleFunction": "",
  1204 + "useCellContentFunction": false,
  1205 + "defaultColumnVisibility": "visible",
  1206 + "columnSelectionToDisplay": "enabled"
  1207 + },
  1208 + "_hash": 0.9934225682766313,
  1209 + "units": null,
  1210 + "decimals": null,
  1211 + "funcBody": null,
  1212 + "usePostProcessing": null,
  1213 + "postFuncBody": null
  1214 + },
  1215 + {
  1216 + "name": "target_sw_version",
  1217 + "type": "timeseries",
  1218 + "label": "Target SW version",
  1219 + "color": "#607d8b",
  1220 + "settings": {
  1221 + "columnWidth": "0px",
  1222 + "useCellStyleFunction": false,
  1223 + "cellStyleFunction": "",
  1224 + "useCellContentFunction": false,
  1225 + "cellContentFunction": "",
  1226 + "defaultColumnVisibility": "visible",
  1227 + "columnSelectionToDisplay": "enabled"
  1228 + },
  1229 + "_hash": 0.5251724416842531,
  1230 + "units": null,
  1231 + "decimals": null,
  1232 + "funcBody": null,
  1233 + "usePostProcessing": null,
  1234 + "postFuncBody": null
  1235 + },
  1236 + {
  1237 + "name": "target_sw_ts",
  1238 + "type": "timeseries",
  1239 + "label": "Target SW set time",
  1240 + "color": "#e91e63",
  1241 + "settings": {
  1242 + "columnWidth": "0px",
  1243 + "useCellStyleFunction": false,
  1244 + "cellStyleFunction": "",
  1245 + "useCellContentFunction": true,
  1246 + "defaultColumnVisibility": "visible",
  1247 + "columnSelectionToDisplay": "enabled",
  1248 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1249 + },
  1250 + "_hash": 0.31823244858578237,
  1251 + "units": null,
  1252 + "decimals": null,
  1253 + "funcBody": null,
  1254 + "usePostProcessing": null,
  1255 + "postFuncBody": null
  1256 + },
  1257 + {
  1258 + "name": "sw_state",
  1259 + "type": "timeseries",
  1260 + "label": "Progress",
  1261 + "color": "#9c27b0",
  1262 + "settings": {
  1263 + "columnWidth": "30%",
  1264 + "useCellStyleFunction": true,
  1265 + "useCellContentFunction": true,
  1266 + "defaultColumnVisibility": "visible",
  1267 + "columnSelectionToDisplay": "enabled",
  1268 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1269 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1270 + },
  1271 + "_hash": 0.8174211757846257,
  1272 + "units": null,
  1273 + "decimals": null,
  1274 + "funcBody": null,
  1275 + "usePostProcessing": null,
  1276 + "postFuncBody": null
  1277 + },
  1278 + {
  1279 + "name": "sw_state",
  1280 + "type": "timeseries",
  1281 + "label": "Status",
  1282 + "color": "#f44336",
  1283 + "settings": {
  1284 + "columnWidth": "130px",
  1285 + "useCellStyleFunction": true,
  1286 + "useCellContentFunction": true,
  1287 + "defaultColumnVisibility": "visible",
  1288 + "columnSelectionToDisplay": "enabled",
  1289 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1290 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1291 + },
  1292 + "_hash": 0.7764426948615217,
  1293 + "units": null,
  1294 + "decimals": null,
  1295 + "funcBody": null,
  1296 + "usePostProcessing": null,
  1297 + "postFuncBody": null
  1298 + },
  1299 + {
  1300 + "name": "sw_checksum",
  1301 + "type": "attribute",
  1302 + "label": "sw_checksum",
  1303 + "color": "#3f51b5",
  1304 + "settings": {
  1305 + "columnWidth": "0px",
  1306 + "useCellStyleFunction": false,
  1307 + "cellStyleFunction": "",
  1308 + "useCellContentFunction": false,
  1309 + "defaultColumnVisibility": "hidden",
  1310 + "columnSelectionToDisplay": "disabled"
  1311 + },
  1312 + "_hash": 0.5594087842471693,
  1313 + "units": null,
  1314 + "decimals": null,
  1315 + "funcBody": null,
  1316 + "usePostProcessing": null,
  1317 + "postFuncBody": null
  1318 + },
  1319 + {
  1320 + "name": "sw_url",
  1321 + "type": "attribute",
  1322 + "label": "sw_url",
  1323 + "color": "#e91e63",
  1324 + "settings": {
  1325 + "columnWidth": "0px",
  1326 + "useCellStyleFunction": false,
  1327 + "cellStyleFunction": "",
  1328 + "useCellContentFunction": false,
  1329 + "cellContentFunction": "",
  1330 + "defaultColumnVisibility": "hidden",
  1331 + "columnSelectionToDisplay": "disabled"
  1332 + },
  1333 + "_hash": 0.3355829384124256,
  1334 + "units": null,
  1335 + "decimals": null,
  1336 + "funcBody": null,
  1337 + "usePostProcessing": null,
  1338 + "postFuncBody": null
  1339 + }
  1340 + ]
  1341 + }
  1342 + ],
  1343 + "actions": {
  1344 + "actionCellButton": [
  1345 + {
  1346 + "name": "History software update",
  1347 + "icon": "history",
  1348 + "type": "openDashboardState",
  1349 + "targetDashboardStateId": "device_software_history",
  1350 + "setEntityId": true,
  1351 + "stateEntityParamName": null,
  1352 + "openInSeparateDialog": false,
  1353 + "dialogTitle": "",
  1354 + "dialogHideDashboardToolbar": true,
  1355 + "dialogWidth": null,
  1356 + "dialogHeight": null,
  1357 + "openRightLayout": false,
  1358 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1359 + },
  1360 + {
  1361 + "name": "Edit software",
  1362 + "icon": "edit",
  1363 + "type": "customPretty",
  1364 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1365 + "customCss": "form {\n min-width: 300px !important;\n}",
  1366 + "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}",
  1367 + "customResources": [],
  1368 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1369 + },
  1370 + {
  1371 + "name": "Download software",
  1372 + "icon": "file_download",
  1373 + "type": "custom",
  1374 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1375 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1376 + },
  1377 + {
  1378 + "name": "Copy checksum/URL",
  1379 + "icon": "content_copy",
  1380 + "type": "custom",
  1381 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1382 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1383 + }
  1384 + ]
  1385 + },
  1386 + "showTitleIcon": false,
  1387 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1388 + "iconSize": "24px",
  1389 + "titleTooltip": "",
  1390 + "widgetStyle": {}
  1391 + },
  1392 + "row": 0,
  1393 + "col": 0,
  1394 + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91"
  1395 + },
  1396 + "3624013b-378c-f110-5eba-ae95c25a4dcc": {
  1397 + "isSystemType": true,
  1398 + "bundleAlias": "cards",
  1399 + "typeAlias": "entities_table",
  1400 + "type": "latest",
  1401 + "title": "New widget",
  1402 + "image": null,
  1403 + "description": null,
  1404 + "sizeX": 7.5,
  1405 + "sizeY": 6.5,
  1406 + "config": {
  1407 + "timewindow": {
  1408 + "realtime": {
  1409 + "interval": 1000,
  1410 + "timewindowMs": 86400000
  1411 + },
  1412 + "aggregation": {
  1413 + "type": "NONE",
  1414 + "limit": 200
  1415 + }
  1416 + },
  1417 + "showTitle": true,
  1418 + "backgroundColor": "rgb(255, 255, 255)",
  1419 + "color": "rgba(0, 0, 0, 0.87)",
  1420 + "padding": "4px",
  1421 + "settings": {
  1422 + "enableSearch": true,
  1423 + "displayPagination": true,
  1424 + "defaultPageSize": 10,
  1425 + "defaultSortOrder": "entityLabel",
  1426 + "displayEntityName": false,
  1427 + "displayEntityType": false,
  1428 + "enableSelectColumnDisplay": false,
  1429 + "enableStickyHeader": true,
  1430 + "enableStickyAction": true,
  1431 + "entitiesTitle": "Devices",
  1432 + "displayEntityLabel": true,
  1433 + "entityLabelColumnTitle": "Device"
  1434 + },
  1435 + "title": "New Entities table",
  1436 + "dropShadow": true,
  1437 + "enableFullscreen": true,
  1438 + "titleStyle": {
  1439 + "fontSize": "16px",
  1440 + "fontWeight": 400,
  1441 + "padding": "5px 10px 5px 10px"
  1442 + },
  1443 + "useDashboardTimewindow": false,
  1444 + "showLegend": false,
  1445 + "datasources": [
  1446 + {
  1447 + "type": "entity",
  1448 + "name": null,
  1449 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1450 + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  1451 + "dataKeys": [
  1452 + {
  1453 + "name": "current_sw_title",
  1454 + "type": "timeseries",
  1455 + "label": "Current SW title",
  1456 + "color": "#2196f3",
  1457 + "settings": {
  1458 + "columnWidth": "0px",
  1459 + "useCellStyleFunction": false,
  1460 + "cellStyleFunction": "",
  1461 + "useCellContentFunction": false,
  1462 + "defaultColumnVisibility": "visible",
  1463 + "columnSelectionToDisplay": "enabled"
  1464 + },
  1465 + "_hash": 0.09545533885166413,
  1466 + "units": null,
  1467 + "decimals": null,
  1468 + "funcBody": null,
  1469 + "usePostProcessing": null,
  1470 + "postFuncBody": null
  1471 + },
  1472 + {
  1473 + "name": "current_sw_version",
  1474 + "type": "timeseries",
  1475 + "label": "Current SW version",
  1476 + "color": "#4caf50",
  1477 + "settings": {
  1478 + "columnWidth": "0px",
  1479 + "useCellStyleFunction": false,
  1480 + "cellStyleFunction": "",
  1481 + "useCellContentFunction": false,
  1482 + "defaultColumnVisibility": "visible",
  1483 + "columnSelectionToDisplay": "enabled"
  1484 + },
  1485 + "_hash": 0.7206056602328659,
  1486 + "units": null,
  1487 + "decimals": null,
  1488 + "funcBody": null,
  1489 + "usePostProcessing": null,
  1490 + "postFuncBody": null
  1491 + },
  1492 + {
  1493 + "name": "target_sw_title",
  1494 + "type": "timeseries",
  1495 + "label": "Target SW title",
  1496 + "color": "#ffc107",
  1497 + "settings": {
  1498 + "columnWidth": "0px",
  1499 + "useCellStyleFunction": false,
  1500 + "cellStyleFunction": "",
  1501 + "useCellContentFunction": false,
  1502 + "defaultColumnVisibility": "visible",
  1503 + "columnSelectionToDisplay": "enabled"
  1504 + },
  1505 + "_hash": 0.9934225682766313,
  1506 + "units": null,
  1507 + "decimals": null,
  1508 + "funcBody": null,
  1509 + "usePostProcessing": null,
  1510 + "postFuncBody": null
  1511 + },
  1512 + {
  1513 + "name": "target_sw_version",
  1514 + "type": "timeseries",
  1515 + "label": "Target SW version",
  1516 + "color": "#607d8b",
  1517 + "settings": {
  1518 + "columnWidth": "0px",
  1519 + "useCellStyleFunction": false,
  1520 + "cellStyleFunction": "",
  1521 + "useCellContentFunction": false,
  1522 + "cellContentFunction": "",
  1523 + "defaultColumnVisibility": "visible",
  1524 + "columnSelectionToDisplay": "enabled"
  1525 + },
  1526 + "_hash": 0.5251724416842531,
  1527 + "units": null,
  1528 + "decimals": null,
  1529 + "funcBody": null,
  1530 + "usePostProcessing": null,
  1531 + "postFuncBody": null
  1532 + },
  1533 + {
  1534 + "name": "target_sw_ts",
  1535 + "type": "timeseries",
  1536 + "label": "Target SW set time",
  1537 + "color": "#e91e63",
  1538 + "settings": {
  1539 + "columnWidth": "0px",
  1540 + "useCellStyleFunction": false,
  1541 + "cellStyleFunction": "",
  1542 + "useCellContentFunction": true,
  1543 + "defaultColumnVisibility": "visible",
  1544 + "columnSelectionToDisplay": "enabled",
  1545 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1546 + },
  1547 + "_hash": 0.31823244858578237,
  1548 + "units": null,
  1549 + "decimals": null,
  1550 + "funcBody": null,
  1551 + "usePostProcessing": null,
  1552 + "postFuncBody": null
  1553 + },
  1554 + {
  1555 + "name": "sw_state",
  1556 + "type": "timeseries",
  1557 + "label": "Progress",
  1558 + "color": "#9c27b0",
  1559 + "settings": {
  1560 + "columnWidth": "30%",
  1561 + "useCellStyleFunction": true,
  1562 + "useCellContentFunction": true,
  1563 + "defaultColumnVisibility": "visible",
  1564 + "columnSelectionToDisplay": "enabled",
  1565 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1566 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1567 + },
  1568 + "_hash": 0.8174211757846257,
  1569 + "units": null,
  1570 + "decimals": null,
  1571 + "funcBody": null,
  1572 + "usePostProcessing": null,
  1573 + "postFuncBody": null
  1574 + },
  1575 + {
  1576 + "name": "sw_state",
  1577 + "type": "timeseries",
  1578 + "label": "Status",
  1579 + "color": "#f44336",
  1580 + "settings": {
  1581 + "columnWidth": "130px",
  1582 + "useCellStyleFunction": true,
  1583 + "useCellContentFunction": true,
  1584 + "defaultColumnVisibility": "visible",
  1585 + "columnSelectionToDisplay": "enabled",
  1586 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1587 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1588 + },
  1589 + "_hash": 0.7764426948615217,
  1590 + "units": null,
  1591 + "decimals": null,
  1592 + "funcBody": null,
  1593 + "usePostProcessing": null,
  1594 + "postFuncBody": null
  1595 + },
  1596 + {
  1597 + "name": "sw_checksum",
  1598 + "type": "attribute",
  1599 + "label": "sw_checksum",
  1600 + "color": "#3f51b5",
  1601 + "settings": {
  1602 + "columnWidth": "0px",
  1603 + "useCellStyleFunction": false,
  1604 + "cellStyleFunction": "",
  1605 + "useCellContentFunction": false,
  1606 + "defaultColumnVisibility": "hidden",
  1607 + "columnSelectionToDisplay": "disabled"
  1608 + },
  1609 + "_hash": 0.5594087842471693,
  1610 + "units": null,
  1611 + "decimals": null,
  1612 + "funcBody": null,
  1613 + "usePostProcessing": null,
  1614 + "postFuncBody": null
  1615 + },
  1616 + {
  1617 + "name": "sw_url",
  1618 + "type": "attribute",
  1619 + "label": "sw_url",
  1620 + "color": "#e91e63",
  1621 + "settings": {
  1622 + "columnWidth": "0px",
  1623 + "useCellStyleFunction": false,
  1624 + "cellStyleFunction": "",
  1625 + "useCellContentFunction": false,
  1626 + "cellContentFunction": "",
  1627 + "defaultColumnVisibility": "hidden",
  1628 + "columnSelectionToDisplay": "disabled"
  1629 + },
  1630 + "_hash": 0.3355829384124256,
  1631 + "units": null,
  1632 + "decimals": null,
  1633 + "funcBody": null,
  1634 + "usePostProcessing": null,
  1635 + "postFuncBody": null
  1636 + }
  1637 + ]
  1638 + }
  1639 + ],
  1640 + "actions": {
  1641 + "actionCellButton": [
  1642 + {
  1643 + "name": "History software update",
  1644 + "icon": "history",
  1645 + "type": "openDashboardState",
  1646 + "targetDashboardStateId": "device_software_history",
  1647 + "setEntityId": true,
  1648 + "stateEntityParamName": null,
  1649 + "openInSeparateDialog": false,
  1650 + "dialogTitle": "",
  1651 + "dialogHideDashboardToolbar": true,
  1652 + "dialogWidth": null,
  1653 + "dialogHeight": null,
  1654 + "openRightLayout": false,
  1655 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1656 + },
  1657 + {
  1658 + "name": "Edit software",
  1659 + "icon": "edit",
  1660 + "type": "customPretty",
  1661 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1662 + "customCss": "form {\n min-width: 300px !important;\n}",
  1663 + "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}",
  1664 + "customResources": [],
  1665 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1666 + },
  1667 + {
  1668 + "name": "Download software",
  1669 + "icon": "file_download",
  1670 + "type": "custom",
  1671 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1672 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1673 + },
  1674 + {
  1675 + "name": "Copy checksum/URL",
  1676 + "icon": "content_copy",
  1677 + "type": "custom",
  1678 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1679 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1680 + }
  1681 + ]
  1682 + },
  1683 + "showTitleIcon": false,
  1684 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1685 + "iconSize": "24px",
  1686 + "titleTooltip": "",
  1687 + "widgetStyle": {}
  1688 + },
  1689 + "row": 0,
  1690 + "col": 0,
  1691 + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc"
  1692 + },
  1693 + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
  1694 + "isSystemType": true,
  1695 + "bundleAlias": "cards",
  1696 + "typeAlias": "entities_table",
  1697 + "type": "latest",
  1698 + "title": "New widget",
  1699 + "image": null,
  1700 + "description": null,
  1701 + "sizeX": 7.5,
  1702 + "sizeY": 6.5,
  1703 + "config": {
  1704 + "timewindow": {
  1705 + "realtime": {
  1706 + "interval": 1000,
  1707 + "timewindowMs": 86400000
  1708 + },
  1709 + "aggregation": {
  1710 + "type": "NONE",
  1711 + "limit": 200
  1712 + }
  1713 + },
  1714 + "showTitle": true,
  1715 + "backgroundColor": "rgb(255, 255, 255)",
  1716 + "color": "rgba(0, 0, 0, 0.87)",
  1717 + "padding": "4px",
  1718 + "settings": {
  1719 + "enableSearch": true,
  1720 + "displayPagination": true,
  1721 + "defaultPageSize": 10,
  1722 + "defaultSortOrder": "entityLabel",
  1723 + "displayEntityName": false,
  1724 + "displayEntityType": false,
  1725 + "enableSelectColumnDisplay": false,
  1726 + "enableStickyHeader": true,
  1727 + "enableStickyAction": true,
  1728 + "entitiesTitle": "Devices",
  1729 + "displayEntityLabel": true,
  1730 + "entityLabelColumnTitle": "Device"
  1731 + },
  1732 + "title": "New Entities table",
  1733 + "dropShadow": true,
  1734 + "enableFullscreen": true,
  1735 + "titleStyle": {
  1736 + "fontSize": "16px",
  1737 + "fontWeight": 400,
  1738 + "padding": "5px 10px 5px 10px"
  1739 + },
  1740 + "useDashboardTimewindow": false,
  1741 + "showLegend": false,
  1742 + "datasources": [
  1743 + {
  1744 + "type": "entity",
  1745 + "name": null,
  1746 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1747 + "filterId": "6044e198-df64-cd76-f339-696f220c4943",
  1748 + "dataKeys": [
  1749 + {
  1750 + "name": "current_sw_title",
  1751 + "type": "timeseries",
  1752 + "label": "Current SW title",
  1753 + "color": "#2196f3",
  1754 + "settings": {
  1755 + "columnWidth": "0px",
  1756 + "useCellStyleFunction": false,
  1757 + "cellStyleFunction": "",
  1758 + "useCellContentFunction": false,
  1759 + "defaultColumnVisibility": "visible",
  1760 + "columnSelectionToDisplay": "enabled"
  1761 + },
  1762 + "_hash": 0.09545533885166413,
  1763 + "units": null,
  1764 + "decimals": null,
  1765 + "funcBody": null,
  1766 + "usePostProcessing": null,
  1767 + "postFuncBody": null
  1768 + },
  1769 + {
  1770 + "name": "current_sw_version",
  1771 + "type": "timeseries",
  1772 + "label": "Current SW version",
  1773 + "color": "#4caf50",
  1774 + "settings": {
  1775 + "columnWidth": "0px",
  1776 + "useCellStyleFunction": false,
  1777 + "cellStyleFunction": "",
  1778 + "useCellContentFunction": false,
  1779 + "defaultColumnVisibility": "visible",
  1780 + "columnSelectionToDisplay": "enabled"
  1781 + },
  1782 + "_hash": 0.7206056602328659,
  1783 + "units": null,
  1784 + "decimals": null,
  1785 + "funcBody": null,
  1786 + "usePostProcessing": null,
  1787 + "postFuncBody": null
  1788 + },
  1789 + {
  1790 + "name": "target_sw_title",
  1791 + "type": "timeseries",
  1792 + "label": "Target SW title",
  1793 + "color": "#ffc107",
  1794 + "settings": {
  1795 + "columnWidth": "0px",
  1796 + "useCellStyleFunction": false,
  1797 + "cellStyleFunction": "",
  1798 + "useCellContentFunction": false,
  1799 + "defaultColumnVisibility": "visible",
  1800 + "columnSelectionToDisplay": "enabled"
  1801 + },
  1802 + "_hash": 0.9934225682766313,
  1803 + "units": null,
  1804 + "decimals": null,
  1805 + "funcBody": null,
  1806 + "usePostProcessing": null,
  1807 + "postFuncBody": null
  1808 + },
  1809 + {
  1810 + "name": "target_sw_version",
  1811 + "type": "timeseries",
  1812 + "label": "Target SW version",
  1813 + "color": "#607d8b",
  1814 + "settings": {
  1815 + "columnWidth": "0px",
  1816 + "useCellStyleFunction": false,
  1817 + "cellStyleFunction": "",
  1818 + "useCellContentFunction": false,
  1819 + "cellContentFunction": "",
  1820 + "defaultColumnVisibility": "visible",
  1821 + "columnSelectionToDisplay": "enabled"
  1822 + },
  1823 + "_hash": 0.5251724416842531,
  1824 + "units": null,
  1825 + "decimals": null,
  1826 + "funcBody": null,
  1827 + "usePostProcessing": null,
  1828 + "postFuncBody": null
  1829 + },
  1830 + {
  1831 + "name": "target_sw_ts",
  1832 + "type": "timeseries",
  1833 + "label": "Target SW set time",
  1834 + "color": "#e91e63",
  1835 + "settings": {
  1836 + "columnWidth": "0px",
  1837 + "useCellStyleFunction": false,
  1838 + "cellStyleFunction": "",
  1839 + "useCellContentFunction": true,
  1840 + "defaultColumnVisibility": "visible",
  1841 + "columnSelectionToDisplay": "enabled",
  1842 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1843 + },
  1844 + "_hash": 0.31823244858578237,
  1845 + "units": null,
  1846 + "decimals": null,
  1847 + "funcBody": null,
  1848 + "usePostProcessing": null,
  1849 + "postFuncBody": null
  1850 + },
  1851 + {
  1852 + "name": "sw_state",
  1853 + "type": "timeseries",
  1854 + "label": "Progress",
  1855 + "color": "#9c27b0",
  1856 + "settings": {
  1857 + "columnWidth": "30%",
  1858 + "useCellStyleFunction": true,
  1859 + "useCellContentFunction": true,
  1860 + "defaultColumnVisibility": "visible",
  1861 + "columnSelectionToDisplay": "enabled",
  1862 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1863 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1864 + },
  1865 + "_hash": 0.8174211757846257,
  1866 + "units": null,
  1867 + "decimals": null,
  1868 + "funcBody": null,
  1869 + "usePostProcessing": null,
  1870 + "postFuncBody": null
  1871 + },
  1872 + {
  1873 + "name": "sw_state",
  1874 + "type": "timeseries",
  1875 + "label": "Status",
  1876 + "color": "#f44336",
  1877 + "settings": {
  1878 + "columnWidth": "130px",
  1879 + "useCellStyleFunction": true,
  1880 + "useCellContentFunction": true,
  1881 + "defaultColumnVisibility": "visible",
  1882 + "columnSelectionToDisplay": "enabled",
  1883 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1884 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1885 + },
  1886 + "_hash": 0.7764426948615217,
  1887 + "units": null,
  1888 + "decimals": null,
  1889 + "funcBody": null,
  1890 + "usePostProcessing": null,
  1891 + "postFuncBody": null
  1892 + },
  1893 + {
  1894 + "name": "sw_checksum",
  1895 + "type": "attribute",
  1896 + "label": "sw_checksum",
  1897 + "color": "#3f51b5",
  1898 + "settings": {
  1899 + "columnWidth": "0px",
  1900 + "useCellStyleFunction": false,
  1901 + "cellStyleFunction": "",
  1902 + "useCellContentFunction": false,
  1903 + "defaultColumnVisibility": "hidden",
  1904 + "columnSelectionToDisplay": "disabled"
  1905 + },
  1906 + "_hash": 0.5594087842471693,
  1907 + "units": null,
  1908 + "decimals": null,
  1909 + "funcBody": null,
  1910 + "usePostProcessing": null,
  1911 + "postFuncBody": null
  1912 + },
  1913 + {
  1914 + "name": "sw_url",
  1915 + "type": "attribute",
  1916 + "label": "sw_url",
  1917 + "color": "#e91e63",
  1918 + "settings": {
  1919 + "columnWidth": "0px",
  1920 + "useCellStyleFunction": false,
  1921 + "cellStyleFunction": "",
  1922 + "useCellContentFunction": false,
  1923 + "cellContentFunction": "",
  1924 + "defaultColumnVisibility": "hidden",
  1925 + "columnSelectionToDisplay": "disabled"
  1926 + },
  1927 + "_hash": 0.3355829384124256,
  1928 + "units": null,
  1929 + "decimals": null,
  1930 + "funcBody": null,
  1931 + "usePostProcessing": null,
  1932 + "postFuncBody": null
  1933 + }
  1934 + ]
  1935 + }
  1936 + ],
  1937 + "actions": {
  1938 + "actionCellButton": [
  1939 + {
  1940 + "name": "History software update",
  1941 + "icon": "history",
  1942 + "type": "openDashboardState",
  1943 + "targetDashboardStateId": "device_software_history",
  1944 + "setEntityId": true,
  1945 + "stateEntityParamName": null,
  1946 + "openInSeparateDialog": false,
  1947 + "dialogTitle": "",
  1948 + "dialogHideDashboardToolbar": true,
  1949 + "dialogWidth": null,
  1950 + "dialogHeight": null,
  1951 + "openRightLayout": false,
  1952 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1953 + },
  1954 + {
  1955 + "name": "Edit software",
  1956 + "icon": "edit",
  1957 + "type": "customPretty",
  1958 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1959 + "customCss": "form {\n min-width: 300px !important;\n}",
  1960 + "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}",
  1961 + "customResources": [],
  1962 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1963 + },
  1964 + {
  1965 + "name": "Download software",
  1966 + "icon": "file_download",
  1967 + "type": "custom",
  1968 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1969 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1970 + },
  1971 + {
  1972 + "name": "Copy checksum/URL",
  1973 + "icon": "content_copy",
  1974 + "type": "custom",
  1975 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}",
  1976 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1977 + }
  1978 + ]
  1979 + },
  1980 + "showTitleIcon": false,
  1981 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1982 + "iconSize": "24px",
  1983 + "titleTooltip": "",
  1984 + "widgetStyle": {}
  1985 + },
  1986 + "row": 0,
  1987 + "col": 0,
  1988 + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176"
  1989 + }
  1990 + },
  1991 + "states": {
  1992 + "default": {
  1993 + "name": "Device list",
  1994 + "root": true,
  1995 + "layouts": {
  1996 + "main": {
  1997 + "widgets": {
  1998 + "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
  1999 + "sizeX": 19,
  2000 + "sizeY": 12,
  2001 + "row": 0,
  2002 + "col": 0
  2003 + },
  2004 + "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
  2005 + "sizeX": 5,
  2006 + "sizeY": 3,
  2007 + "row": 0,
  2008 + "col": 19
  2009 + },
  2010 + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
  2011 + "sizeX": 5,
  2012 + "sizeY": 3,
  2013 + "row": 3,
  2014 + "col": 19
  2015 + },
  2016 + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
  2017 + "sizeX": 5,
  2018 + "sizeY": 3,
  2019 + "row": 9,
  2020 + "col": 19
  2021 + },
  2022 + "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
  2023 + "sizeX": 5,
  2024 + "sizeY": 3,
  2025 + "row": 6,
  2026 + "col": 19
  2027 + }
  2028 + },
  2029 + "gridSettings": {
  2030 + "backgroundColor": "#eeeeee",
  2031 + "color": "rgba(0,0,0,0.870588)",
  2032 + "columns": 24,
  2033 + "margin": 12,
  2034 + "backgroundSizeMode": "100%",
  2035 + "autoFillHeight": true,
  2036 + "backgroundImageUrl": null,
  2037 + "mobileAutoFillHeight": true,
  2038 + "mobileRowHeight": 70
  2039 + }
  2040 + }
  2041 + }
  2042 + },
  2043 + "device_software_history": {
  2044 + "name": "Software history: ${entityName}",
  2045 + "root": false,
  2046 + "layouts": {
  2047 + "main": {
  2048 + "widgets": {
  2049 + "100b756c-0082-6505-3ae1-3603e6deea48": {
  2050 + "sizeX": 24,
  2051 + "sizeY": 12,
  2052 + "row": 0,
  2053 + "col": 0
  2054 + }
  2055 + },
  2056 + "gridSettings": {
  2057 + "backgroundColor": "#eeeeee",
  2058 + "color": "rgba(0,0,0,0.870588)",
  2059 + "columns": 24,
  2060 + "margin": 10,
  2061 + "backgroundSizeMode": "100%",
  2062 + "autoFillHeight": true,
  2063 + "backgroundImageUrl": null,
  2064 + "mobileAutoFillHeight": false,
  2065 + "mobileRowHeight": 70
  2066 + }
  2067 + }
  2068 + }
  2069 + },
  2070 + "device_waiting": {
  2071 + "name": "Device waiting",
  2072 + "root": false,
  2073 + "layouts": {
  2074 + "main": {
  2075 + "widgets": {
  2076 + "21be08bb-ec90-f760-ad6f-e7678f12c401": {
  2077 + "sizeX": 24,
  2078 + "sizeY": 12,
  2079 + "row": 0,
  2080 + "col": 0
  2081 + }
  2082 + },
  2083 + "gridSettings": {
  2084 + "backgroundColor": "#eeeeee",
  2085 + "color": "rgba(0,0,0,0.870588)",
  2086 + "columns": 24,
  2087 + "margin": 10,
  2088 + "backgroundSizeMode": "100%",
  2089 + "autoFillHeight": true,
  2090 + "backgroundImageUrl": null,
  2091 + "mobileAutoFillHeight": false,
  2092 + "mobileRowHeight": 70
  2093 + }
  2094 + }
  2095 + }
  2096 + },
  2097 + "device_updating": {
  2098 + "name": "Device updating",
  2099 + "root": false,
  2100 + "layouts": {
  2101 + "main": {
  2102 + "widgets": {
  2103 + "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
  2104 + "sizeX": 24,
  2105 + "sizeY": 12,
  2106 + "row": 0,
  2107 + "col": 0
  2108 + }
  2109 + },
  2110 + "gridSettings": {
  2111 + "backgroundColor": "#eeeeee",
  2112 + "color": "rgba(0,0,0,0.870588)",
  2113 + "columns": 24,
  2114 + "margin": 10,
  2115 + "backgroundSizeMode": "100%",
  2116 + "autoFillHeight": true,
  2117 + "backgroundImageUrl": null,
  2118 + "mobileAutoFillHeight": false,
  2119 + "mobileRowHeight": 70
  2120 + }
  2121 + }
  2122 + }
  2123 + },
  2124 + "device_updated": {
  2125 + "name": "Device updated",
  2126 + "root": false,
  2127 + "layouts": {
  2128 + "main": {
  2129 + "widgets": {
  2130 + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
  2131 + "sizeX": 27,
  2132 + "sizeY": 12,
  2133 + "row": 0,
  2134 + "col": 0
  2135 + }
  2136 + },
  2137 + "gridSettings": {
  2138 + "backgroundColor": "#eeeeee",
  2139 + "color": "rgba(0,0,0,0.870588)",
  2140 + "columns": 24,
  2141 + "margin": 10,
  2142 + "backgroundSizeMode": "100%",
  2143 + "autoFillHeight": true,
  2144 + "backgroundImageUrl": null,
  2145 + "mobileAutoFillHeight": false,
  2146 + "mobileRowHeight": 70
  2147 + }
  2148 + }
  2149 + }
  2150 + },
  2151 + "device_error": {
  2152 + "name": "Device failed",
  2153 + "root": false,
  2154 + "layouts": {
  2155 + "main": {
  2156 + "widgets": {
  2157 + "3624013b-378c-f110-5eba-ae95c25a4dcc": {
  2158 + "sizeX": 24,
  2159 + "sizeY": 12,
  2160 + "row": 0,
  2161 + "col": 0
  2162 + }
  2163 + },
  2164 + "gridSettings": {
  2165 + "backgroundColor": "#eeeeee",
  2166 + "color": "rgba(0,0,0,0.870588)",
  2167 + "columns": 24,
  2168 + "margin": 10,
  2169 + "backgroundSizeMode": "100%",
  2170 + "autoFillHeight": true,
  2171 + "backgroundImageUrl": null,
  2172 + "mobileAutoFillHeight": false,
  2173 + "mobileRowHeight": 70
  2174 + }
  2175 + }
  2176 + }
  2177 + }
  2178 + },
  2179 + "entityAliases": {
  2180 + "639da5b4-31f0-0151-6282-c37a3897b7e8": {
  2181 + "id": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  2182 + "alias": "All devices",
  2183 + "filter": {
  2184 + "type": "entityType",
  2185 + "resolveMultiple": true,
  2186 + "entityType": "DEVICE"
  2187 + }
  2188 + },
  2189 + "19f41c21-d9af-e666-8f50-e1748778f955": {
  2190 + "id": "19f41c21-d9af-e666-8f50-e1748778f955",
  2191 + "alias": "State entity",
  2192 + "filter": {
  2193 + "type": "stateEntity",
  2194 + "resolveMultiple": false,
  2195 + "stateEntityParamName": null,
  2196 + "defaultStateEntity": null
  2197 + }
  2198 + }
  2199 + },
  2200 + "filters": {
  2201 + "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e": {
  2202 + "id": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  2203 + "filter": "WaitingDevicesFilter",
  2204 + "keyFilters": [
  2205 + {
  2206 + "key": {
  2207 + "type": "TIME_SERIES",
  2208 + "key": "sw_state"
  2209 + },
  2210 + "valueType": "STRING",
  2211 + "predicates": [
  2212 + {
  2213 + "keyFilterPredicate": {
  2214 + "operation": "EQUAL",
  2215 + "value": {
  2216 + "defaultValue": "QUEUED",
  2217 + "dynamicValue": null
  2218 + },
  2219 + "ignoreCase": false,
  2220 + "type": "STRING"
  2221 + },
  2222 + "userInfo": {
  2223 + "editable": true,
  2224 + "label": "",
  2225 + "autogeneratedLabel": true,
  2226 + "order": 0
  2227 + }
  2228 + }
  2229 + ]
  2230 + }
  2231 + ],
  2232 + "editable": false
  2233 + },
  2234 + "579f0468-9ce9-7e3e-b34c-88dd3de59897": {
  2235 + "id": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  2236 + "filter": "UpdatingDevicesFilter",
  2237 + "keyFilters": [
  2238 + {
  2239 + "key": {
  2240 + "type": "TIME_SERIES",
  2241 + "key": "sw_state"
  2242 + },
  2243 + "valueType": "STRING",
  2244 + "predicates": [
  2245 + {
  2246 + "keyFilterPredicate": {
  2247 + "operation": "OR",
  2248 + "predicates": [
  2249 + {
  2250 + "keyFilterPredicate": {
  2251 + "operation": "EQUAL",
  2252 + "value": {
  2253 + "defaultValue": "INITIATED",
  2254 + "dynamicValue": null
  2255 + },
  2256 + "ignoreCase": false,
  2257 + "type": "STRING"
  2258 + },
  2259 + "userInfo": {
  2260 + "editable": false,
  2261 + "label": "sw_state equel",
  2262 + "autogeneratedLabel": true,
  2263 + "order": 0
  2264 + }
  2265 + },
  2266 + {
  2267 + "keyFilterPredicate": {
  2268 + "operation": "EQUAL",
  2269 + "value": {
  2270 + "defaultValue": "DOWNLOADING",
  2271 + "dynamicValue": null
  2272 + },
  2273 + "ignoreCase": false,
  2274 + "type": "STRING"
  2275 + },
  2276 + "userInfo": {
  2277 + "editable": false,
  2278 + "label": "sw_state equal",
  2279 + "autogeneratedLabel": true,
  2280 + "order": 0
  2281 + }
  2282 + },
  2283 + {
  2284 + "keyFilterPredicate": {
  2285 + "operation": "EQUAL",
  2286 + "value": {
  2287 + "defaultValue": "DOWNLOADED",
  2288 + "dynamicValue": null
  2289 + },
  2290 + "ignoreCase": false,
  2291 + "type": "STRING"
  2292 + },
  2293 + "userInfo": {
  2294 + "editable": false,
  2295 + "label": "sw_state equal",
  2296 + "autogeneratedLabel": true,
  2297 + "order": 0
  2298 + }
  2299 + },
  2300 + {
  2301 + "keyFilterPredicate": {
  2302 + "operation": "EQUAL",
  2303 + "value": {
  2304 + "defaultValue": "VERIFIED",
  2305 + "dynamicValue": null
  2306 + },
  2307 + "ignoreCase": false,
  2308 + "type": "STRING"
  2309 + },
  2310 + "userInfo": {
  2311 + "editable": false,
  2312 + "label": "sw_state equal",
  2313 + "autogeneratedLabel": true,
  2314 + "order": 0
  2315 + }
  2316 + },
  2317 + {
  2318 + "keyFilterPredicate": {
  2319 + "operation": "EQUAL",
  2320 + "value": {
  2321 + "defaultValue": "UPDATING",
  2322 + "dynamicValue": null
  2323 + },
  2324 + "ignoreCase": false,
  2325 + "type": "STRING"
  2326 + },
  2327 + "userInfo": {
  2328 + "editable": false,
  2329 + "label": "sw_state equal",
  2330 + "autogeneratedLabel": true,
  2331 + "order": 0
  2332 + }
  2333 + }
  2334 + ],
  2335 + "type": "COMPLEX"
  2336 + },
  2337 + "userInfo": {
  2338 + "editable": true,
  2339 + "label": "",
  2340 + "autogeneratedLabel": true,
  2341 + "order": 0
  2342 + }
  2343 + }
  2344 + ]
  2345 + }
  2346 + ],
  2347 + "editable": false
  2348 + },
  2349 + "6044e198-df64-cd76-f339-696f220c4943": {
  2350 + "id": "6044e198-df64-cd76-f339-696f220c4943",
  2351 + "filter": "UpdetedDevicesFilter",
  2352 + "keyFilters": [
  2353 + {
  2354 + "key": {
  2355 + "type": "TIME_SERIES",
  2356 + "key": "sw_state"
  2357 + },
  2358 + "valueType": "STRING",
  2359 + "predicates": [
  2360 + {
  2361 + "keyFilterPredicate": {
  2362 + "operation": "EQUAL",
  2363 + "value": {
  2364 + "defaultValue": "UPDATED",
  2365 + "dynamicValue": null
  2366 + },
  2367 + "ignoreCase": false,
  2368 + "type": "STRING"
  2369 + },
  2370 + "userInfo": {
  2371 + "editable": true,
  2372 + "label": "",
  2373 + "autogeneratedLabel": true,
  2374 + "order": 0
  2375 + }
  2376 + }
  2377 + ]
  2378 + }
  2379 + ],
  2380 + "editable": false
  2381 + },
  2382 + "bdbc6ea1-95a7-3912-341a-58dc7704a00f": {
  2383 + "id": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  2384 + "filter": "FailedDevicesFilter",
  2385 + "keyFilters": [
  2386 + {
  2387 + "key": {
  2388 + "type": "TIME_SERIES",
  2389 + "key": "sw_state"
  2390 + },
  2391 + "valueType": "STRING",
  2392 + "predicates": [
  2393 + {
  2394 + "keyFilterPredicate": {
  2395 + "operation": "EQUAL",
  2396 + "value": {
  2397 + "defaultValue": "FAILED",
  2398 + "dynamicValue": null
  2399 + },
  2400 + "ignoreCase": false,
  2401 + "type": "STRING"
  2402 + },
  2403 + "userInfo": {
  2404 + "editable": true,
  2405 + "label": "",
  2406 + "autogeneratedLabel": true,
  2407 + "order": 0
  2408 + }
  2409 + }
  2410 + ]
  2411 + }
  2412 + ],
  2413 + "editable": false
  2414 + },
  2415 + "8fdb88d0-50ac-2232-fdb7-69c30c16544e": {
  2416 + "id": "8fdb88d0-50ac-2232-fdb7-69c30c16544e",
  2417 + "filter": "DeviceSearch",
  2418 + "keyFilters": [
  2419 + {
  2420 + "key": {
  2421 + "type": "ENTITY_FIELD",
  2422 + "key": "name"
  2423 + },
  2424 + "valueType": "STRING",
  2425 + "predicates": [
  2426 + {
  2427 + "keyFilterPredicate": {
  2428 + "operation": "CONTAINS",
  2429 + "value": {
  2430 + "defaultValue": ""
  2431 + },
  2432 + "ignoreCase": true,
  2433 + "type": "STRING"
  2434 + },
  2435 + "userInfo": {
  2436 + "editable": true,
  2437 + "label": "Device name",
  2438 + "autogeneratedLabel": false,
  2439 + "order": 0
  2440 + }
  2441 + }
  2442 + ]
  2443 + }
  2444 + ],
  2445 + "editable": true
  2446 + }
  2447 + },
  2448 + "timewindow": {
  2449 + "displayValue": "",
  2450 + "hideInterval": false,
  2451 + "hideAggregation": false,
  2452 + "hideAggInterval": false,
  2453 + "hideTimezone": false,
  2454 + "selectedTab": 0,
  2455 + "realtime": {
  2456 + "realtimeType": 0,
  2457 + "interval": 1000,
  2458 + "timewindowMs": 60000,
  2459 + "quickInterval": "CURRENT_DAY"
  2460 + },
  2461 + "history": {
  2462 + "historyType": 0,
  2463 + "interval": 1000,
  2464 + "timewindowMs": 60000,
  2465 + "fixedTimewindow": {
  2466 + "startTimeMs": 1618998609030,
  2467 + "endTimeMs": 1619085009030
  2468 + },
  2469 + "quickInterval": "CURRENT_DAY"
  2470 + },
  2471 + "aggregation": {
  2472 + "type": "AVG",
  2473 + "limit": 25000
  2474 + }
  2475 + },
  2476 + "settings": {
  2477 + "stateControllerId": "entity",
  2478 + "showTitle": false,
  2479 + "showDashboardsSelect": false,
  2480 + "showEntitiesSelect": false,
  2481 + "showDashboardTimewindow": true,
  2482 + "showDashboardExport": false,
  2483 + "toolbarAlwaysOpen": true,
  2484 + "titleColor": "rgba(0,0,0,0.870588)",
  2485 + "showFilters": true,
  2486 + "showDashboardLogo": false,
  2487 + "dashboardLogoUrl": null,
  2488 + "showUpdateDashboardImage": false
  2489 + }
  2490 + },
  2491 + "name": "Software"
  2492 +}
@@ -17,9 +17,9 @@ @@ -17,9 +17,9 @@
17 "sizeY": 5.5, 17 "sizeY": 5.5,
18 "resources": [], 18 "resources": [],
19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", 19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
20 - "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n",  
21 - "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var utils = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('utils'));\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = utils.guid();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n \nself.onDestroy = function() {\n}",  
22 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\"\n ]\n}", 20 + "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n",
  21 + "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\nvar useRowStyleFunction = false;\nvar styleObj = {};\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n if (self.ctx.settings.useRowStyleFunction && self.ctx.settings.rowStyleFunction) {\n try {\n var style = self.ctx.settings.rowStyleFunction;\n styleObj = JSON.parse(style);\n if ((typeof styleObj !== \"object\")) {\n styleObj = null;\n throw new URIError(`${style === null ? 'null' : typeof style} instead of style object`);\n }\n else if (typeof styleObj === \"object\" && (typeof styleObj.length) === \"number\") {\n styleObj = null;\n throw new URIError('Array instead of style object');\n }\n }\n catch (e) {\n console.log(`Row style function in widget ` +\n `returns '${e}'. Please check your row style function.`); \n }\n useRowStyleFunction = self.ctx.settings.useRowStyleFunction;\n \n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n \n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (styleObj && styleObj !== null) {\n terminal.css(styleObj);\n }\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}",
  22 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
23 "dataKeySettingsSchema": "{}\n", 23 "dataKeySettingsSchema": "{}\n",
24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
25 } 25 }
@@ -79,6 +79,68 @@ CREATE TABLE IF NOT EXISTS ota_package ( @@ -79,6 +79,68 @@ CREATE TABLE IF NOT EXISTS ota_package (
79 CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) 79 CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
80 ); 80 );
81 81
  82 +CREATE TABLE IF NOT EXISTS oauth2_params (
  83 + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY,
  84 + enabled boolean,
  85 + tenant_id uuid,
  86 + created_time bigint NOT NULL
  87 +);
  88 +
  89 +CREATE TABLE IF NOT EXISTS oauth2_registration (
  90 + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY,
  91 + oauth2_params_id uuid NOT NULL,
  92 + created_time bigint NOT NULL,
  93 + additional_info varchar,
  94 + client_id varchar(255),
  95 + client_secret varchar(255),
  96 + authorization_uri varchar(255),
  97 + token_uri varchar(255),
  98 + scope varchar(255),
  99 + platforms varchar(255),
  100 + user_info_uri varchar(255),
  101 + user_name_attribute_name varchar(255),
  102 + jwk_set_uri varchar(255),
  103 + client_authentication_method varchar(255),
  104 + login_button_label varchar(255),
  105 + login_button_icon varchar(255),
  106 + allow_user_creation boolean,
  107 + activate_user boolean,
  108 + type varchar(31),
  109 + basic_email_attribute_key varchar(31),
  110 + basic_first_name_attribute_key varchar(31),
  111 + basic_last_name_attribute_key varchar(31),
  112 + basic_tenant_name_strategy varchar(31),
  113 + basic_tenant_name_pattern varchar(255),
  114 + basic_customer_name_pattern varchar(255),
  115 + basic_default_dashboard_name varchar(255),
  116 + basic_always_full_screen boolean,
  117 + custom_url varchar(255),
  118 + custom_username varchar(255),
  119 + custom_password varchar(255),
  120 + custom_send_token boolean,
  121 + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE
  122 +);
  123 +
  124 +CREATE TABLE IF NOT EXISTS oauth2_domain (
  125 + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY,
  126 + oauth2_params_id uuid NOT NULL,
  127 + created_time bigint NOT NULL,
  128 + domain_name varchar(255),
  129 + domain_scheme varchar(31),
  130 + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  131 + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme)
  132 +);
  133 +
  134 +CREATE TABLE IF NOT EXISTS oauth2_mobile (
  135 + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY,
  136 + oauth2_params_id uuid NOT NULL,
  137 + created_time bigint NOT NULL,
  138 + pkg_name varchar(255),
  139 + callback_url_scheme varchar(255),
  140 + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  141 + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name)
  142 +);
  143 +
82 ALTER TABLE dashboard 144 ALTER TABLE dashboard
83 ADD COLUMN IF NOT EXISTS image varchar(1000000); 145 ADD COLUMN IF NOT EXISTS image varchar(1000000);
84 146
@@ -204,6 +204,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -204,6 +204,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
204 syncSessionSet.add(key); 204 syncSessionSet.add(key);
205 } 205 }
206 }); 206 });
  207 + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions);
207 syncSessionSet.forEach(rpcSubscriptions::remove); 208 syncSessionSet.forEach(rpcSubscriptions::remove);
208 } 209 }
209 210
@@ -517,7 +518,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -517,7 +518,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
517 } else { 518 } else {
518 SessionInfoMetaData sessionMD = sessions.get(sessionId); 519 SessionInfoMetaData sessionMD = sessions.get(sessionId);
519 if (sessionMD == null) { 520 if (sessionMD == null) {
520 - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); 521 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
521 } 522 }
522 sessionMD.setSubscribedToAttributes(true); 523 sessionMD.setSubscribedToAttributes(true);
523 log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); 524 log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
@@ -538,7 +539,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -538,7 +539,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
538 } else { 539 } else {
539 SessionInfoMetaData sessionMD = sessions.get(sessionId); 540 SessionInfoMetaData sessionMD = sessions.get(sessionId);
540 if (sessionMD == null) { 541 if (sessionMD == null) {
541 - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); 542 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
542 } 543 }
543 sessionMD.setSubscribedToRPC(true); 544 sessionMD.setSubscribedToRPC(true);
544 log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); 545 log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
@@ -37,6 +37,8 @@ import org.springframework.util.StringUtils; @@ -37,6 +37,8 @@ import org.springframework.util.StringUtils;
37 import org.springframework.web.util.UriComponents; 37 import org.springframework.web.util.UriComponents;
38 import org.springframework.web.util.UriComponentsBuilder; 38 import org.springframework.web.util.UriComponentsBuilder;
39 import org.thingsboard.server.dao.oauth2.OAuth2Configuration; 39 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
  40 +import org.thingsboard.server.dao.oauth2.OAuth2Service;
  41 +import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames;
40 import org.thingsboard.server.utils.MiscUtils; 42 import org.thingsboard.server.utils.MiscUtils;
41 43
42 import javax.servlet.http.HttpServletRequest; 44 import javax.servlet.http.HttpServletRequest;
@@ -46,12 +48,13 @@ import java.security.NoSuchAlgorithmException; @@ -46,12 +48,13 @@ import java.security.NoSuchAlgorithmException;
46 import java.util.Base64; 48 import java.util.Base64;
47 import java.util.HashMap; 49 import java.util.HashMap;
48 import java.util.Map; 50 import java.util.Map;
  51 +import java.util.UUID;
49 52
50 @Service 53 @Service
51 @Slf4j 54 @Slf4j
52 public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { 55 public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
53 - public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";  
54 - public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; 56 + private static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
  57 + private static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/";
55 private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; 58 private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
56 private static final char PATH_DELIMITER = '/'; 59 private static final char PATH_DELIMITER = '/';
57 60
@@ -63,6 +66,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @@ -63,6 +66,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
63 @Autowired 66 @Autowired
64 private ClientRegistrationRepository clientRegistrationRepository; 67 private ClientRegistrationRepository clientRegistrationRepository;
65 68
  69 + @Autowired
  70 + private OAuth2Service oAuth2Service;
  71 +
66 @Autowired(required = false) 72 @Autowired(required = false)
67 private OAuth2Configuration oauth2Configuration; 73 private OAuth2Configuration oauth2Configuration;
68 74
@@ -71,7 +77,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @@ -71,7 +77,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
71 public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { 77 public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
72 String registrationId = this.resolveRegistrationId(request); 78 String registrationId = this.resolveRegistrationId(request);
73 String redirectUriAction = getAction(request, "login"); 79 String redirectUriAction = getAction(request, "login");
74 - return resolve(request, registrationId, redirectUriAction); 80 + String appPackage = getAppPackage(request);
  81 + return resolve(request, registrationId, redirectUriAction, appPackage);
75 } 82 }
76 83
77 @Override 84 @Override
@@ -80,7 +87,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @@ -80,7 +87,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
80 return null; 87 return null;
81 } 88 }
82 String redirectUriAction = getAction(request, "authorize"); 89 String redirectUriAction = getAction(request, "authorize");
83 - return resolve(request, registrationId, redirectUriAction); 90 + String appPackage = getAppPackage(request);
  91 + return resolve(request, registrationId, redirectUriAction, appPackage);
84 } 92 }
85 93
86 private String getAction(HttpServletRequest request, String defaultAction) { 94 private String getAction(HttpServletRequest request, String defaultAction) {
@@ -91,8 +99,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @@ -91,8 +99,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
91 return action; 99 return action;
92 } 100 }
93 101
  102 + private String getAppPackage(HttpServletRequest request) {
  103 + return request.getParameter("pkg");
  104 + }
  105 +
94 @SuppressWarnings("deprecation") 106 @SuppressWarnings("deprecation")
95 - private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { 107 + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage) {
96 if (registrationId == null) { 108 if (registrationId == null) {
97 return null; 109 return null;
98 } 110 }
@@ -104,6 +116,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @@ -104,6 +116,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
104 116
105 Map<String, Object> attributes = new HashMap<>(); 117 Map<String, Object> attributes = new HashMap<>();
106 attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); 118 attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
  119 + if (!StringUtils.isEmpty(appPackage)) {
  120 + String callbackUrlScheme = this.oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), appPackage);
  121 + if (StringUtils.isEmpty(callbackUrlScheme)) {
  122 + throw new IllegalArgumentException("Invalid package: " + appPackage + ". No package info found for Client Registration.");
  123 + } else {
  124 + attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme);
  125 + }
  126 + }
107 127
108 OAuth2AuthorizationRequest.Builder builder; 128 OAuth2AuthorizationRequest.Builder builder;
109 if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { 129 if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
@@ -22,12 +22,15 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -22,12 +22,15 @@ import org.springframework.security.access.prepost.PreAuthorize;
22 import org.springframework.web.bind.annotation.RequestBody; 22 import org.springframework.web.bind.annotation.RequestBody;
23 import org.springframework.web.bind.annotation.RequestMapping; 23 import org.springframework.web.bind.annotation.RequestMapping;
24 import org.springframework.web.bind.annotation.RequestMethod; 24 import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
25 import org.springframework.web.bind.annotation.ResponseBody; 26 import org.springframework.web.bind.annotation.ResponseBody;
26 import org.springframework.web.bind.annotation.ResponseStatus; 27 import org.springframework.web.bind.annotation.ResponseStatus;
27 import org.springframework.web.bind.annotation.RestController; 28 import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.StringUtils;
28 import org.thingsboard.server.common.data.exception.ThingsboardException; 30 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 31 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
30 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; 32 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
  33 +import org.thingsboard.server.common.data.oauth2.PlatformType;
31 import org.thingsboard.server.dao.oauth2.OAuth2Configuration; 34 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
32 import org.thingsboard.server.queue.util.TbCoreComponent; 35 import org.thingsboard.server.queue.util.TbCoreComponent;
33 import org.thingsboard.server.service.security.permission.Operation; 36 import org.thingsboard.server.service.security.permission.Operation;
@@ -49,7 +52,9 @@ public class OAuth2Controller extends BaseController { @@ -49,7 +52,9 @@ public class OAuth2Controller extends BaseController {
49 52
50 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) 53 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
51 @ResponseBody 54 @ResponseBody
52 - public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { 55 + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request,
  56 + @RequestParam(required = false) String pkgName,
  57 + @RequestParam(required = false) String platform) throws ThingsboardException {
53 try { 58 try {
54 if (log.isDebugEnabled()) { 59 if (log.isDebugEnabled()) {
55 log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); 60 log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
@@ -59,7 +64,13 @@ public class OAuth2Controller extends BaseController { @@ -59,7 +64,13 @@ public class OAuth2Controller extends BaseController {
59 log.debug("Header: {} {}", header, request.getHeader(header)); 64 log.debug("Header: {} {}", header, request.getHeader(header));
60 } 65 }
61 } 66 }
62 - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); 67 + PlatformType platformType = null;
  68 + if (StringUtils.isNotEmpty(platform)) {
  69 + try {
  70 + platformType = PlatformType.valueOf(platform);
  71 + } catch (Exception e) {}
  72 + }
  73 + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType);
63 } catch (Exception e) { 74 } catch (Exception e) {
64 throw handleException(e); 75 throw handleException(e);
65 } 76 }
@@ -68,10 +79,10 @@ public class OAuth2Controller extends BaseController { @@ -68,10 +79,10 @@ public class OAuth2Controller extends BaseController {
68 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") 79 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
69 @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") 80 @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json")
70 @ResponseBody 81 @ResponseBody
71 - public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { 82 + public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException {
72 try { 83 try {
73 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); 84 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
74 - return oAuth2Service.findOAuth2Params(); 85 + return oAuth2Service.findOAuth2Info();
75 } catch (Exception e) { 86 } catch (Exception e) {
76 throw handleException(e); 87 throw handleException(e);
77 } 88 }
@@ -80,11 +91,11 @@ public class OAuth2Controller extends BaseController { @@ -80,11 +91,11 @@ public class OAuth2Controller extends BaseController {
80 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") 91 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
81 @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) 92 @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
82 @ResponseStatus(value = HttpStatus.OK) 93 @ResponseStatus(value = HttpStatus.OK)
83 - public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { 94 + public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException {
84 try { 95 try {
85 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); 96 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE);
86 - oAuth2Service.saveOAuth2Params(oauth2Params);  
87 - return oAuth2Service.findOAuth2Params(); 97 + oAuth2Service.saveOAuth2Info(oauth2Info);
  98 + return oAuth2Service.findOAuth2Info();
88 } catch (Exception e) { 99 } catch (Exception e) {
89 throw handleException(e); 100 throw handleException(e);
90 } 101 }
@@ -199,6 +199,7 @@ public class ThingsboardInstallService { @@ -199,6 +199,7 @@ public class ThingsboardInstallService {
199 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); 199 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
200 200
201 dataUpdateService.updateData("3.2.2"); 201 dataUpdateService.updateData("3.2.2");
  202 + systemDataLoaderService.createOAuth2Templates();
202 203
203 log.info("Updating system data..."); 204 log.info("Updating system data...");
204 systemDataLoaderService.updateSystemWidgets(); 205 systemDataLoaderService.updateSystemWidgets();
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.context.annotation.Profile; 24 import org.springframework.context.annotation.Profile;
25 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
  26 +import org.thingsboard.common.util.JacksonUtil;
26 import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; 27 import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
27 import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; 28 import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
28 import org.thingsboard.server.common.data.EntityView; 29 import org.thingsboard.server.common.data.EntityView;
@@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId;
35 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; 36 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
36 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 37 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
37 import org.thingsboard.server.common.data.kv.TsKvEntry; 38 import org.thingsboard.server.common.data.kv.TsKvEntry;
  39 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
  40 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
38 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
39 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
40 import org.thingsboard.server.common.data.page.TimePageLink; 43 import org.thingsboard.server.common.data.page.TimePageLink;
@@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao; @@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao;
45 import org.thingsboard.server.dao.alarm.AlarmService; 48 import org.thingsboard.server.dao.alarm.AlarmService;
46 import org.thingsboard.server.dao.entity.EntityService; 49 import org.thingsboard.server.dao.entity.EntityService;
47 import org.thingsboard.server.dao.entityview.EntityViewService; 50 import org.thingsboard.server.dao.entityview.EntityViewService;
  51 +import org.thingsboard.server.dao.oauth2.OAuth2Service;
  52 +import org.thingsboard.server.dao.oauth2.OAuth2Utils;
48 import org.thingsboard.server.dao.rule.RuleChainService; 53 import org.thingsboard.server.dao.rule.RuleChainService;
49 import org.thingsboard.server.dao.tenant.TenantService; 54 import org.thingsboard.server.dao.tenant.TenantService;
50 import org.thingsboard.server.dao.timeseries.TimeseriesService; 55 import org.thingsboard.server.dao.timeseries.TimeseriesService;
51 -import org.thingsboard.common.util.JacksonUtil;  
52 import org.thingsboard.server.service.install.InstallScripts; 56 import org.thingsboard.server.service.install.InstallScripts;
53 57
54 import java.util.ArrayList; 58 import java.util.ArrayList;
@@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
88 @Autowired 92 @Autowired
89 private AlarmDao alarmDao; 93 private AlarmDao alarmDao;
90 94
  95 + @Autowired
  96 + private OAuth2Service oAuth2Service;
  97 +
91 @Override 98 @Override
92 public void updateData(String fromVersion) throws Exception { 99 public void updateData(String fromVersion) throws Exception {
93 switch (fromVersion) { 100 switch (fromVersion) {
@@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
107 log.info("Updating data from version 3.2.2 to 3.3.0 ..."); 114 log.info("Updating data from version 3.2.2 to 3.3.0 ...");
108 tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); 115 tenantsDefaultEdgeRuleChainUpdater.updateEntities(null);
109 tenantsAlarmsCustomerUpdater.updateEntities(null); 116 tenantsAlarmsCustomerUpdater.updateEntities(null);
  117 + updateOAuth2Params();
110 break; 118 break;
111 default: 119 default:
112 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); 120 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
@@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService {
362 } 370 }
363 } 371 }
364 372
  373 + private void updateOAuth2Params() {
  374 + try {
  375 + OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params();
  376 + if (!oauth2ClientsParams.getDomainsParams().isEmpty()) {
  377 + log.info("Updating OAuth2 parameters ...");
  378 + OAuth2Info oAuth2Info = OAuth2Utils.clientParamsToOAuth2Info(oauth2ClientsParams);
  379 + oAuth2Service.saveOAuth2Info(oAuth2Info);
  380 + oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList()));
  381 + log.info("Successfully updated OAuth2 parameters!");
  382 + }
  383 + }
  384 + catch (Exception e) {
  385 + log.error("Failed to update OAuth2 parameters", e);
  386 + }
  387 + }
  388 +
365 } 389 }
@@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService { @@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService {
72 if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { 72 if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
73 try { 73 try {
74 List<ObjectModel> objectModels = 74 List<ObjectModel> objectModels =
75 - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); 75 + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
76 if (!objectModels.isEmpty()) { 76 if (!objectModels.isEmpty()) {
77 ObjectModel objectModel = objectModels.get(0); 77 ObjectModel objectModel = objectModels.get(0);
78 78
79 - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion(); 79 + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version;
80 String name = objectModel.name; 80 String name = objectModel.name;
81 resource.setResourceKey(resourceKey); 81 resource.setResourceKey(resourceKey);
82 if (resource.getId() == null) { 82 if (resource.getId() == null) {
83 - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion()); 83 + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version);
84 } 84 }
85 resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); 85 resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name);
86 } else { 86 } else {
@@ -176,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -176,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService {
176 try { 176 try {
177 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); 177 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
178 List<ObjectModel> objectModels = 178 List<ObjectModel> objectModels =
179 - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); 179 + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
180 if (objectModels.size() == 0) { 180 if (objectModels.size() == 0) {
181 return null; 181 return null;
182 } else { 182 } else {
@@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
33 import org.thingsboard.server.common.data.id.DashboardId; 33 import org.thingsboard.server.common.data.id.DashboardId;
34 import org.thingsboard.server.common.data.id.IdBased; 34 import org.thingsboard.server.common.data.id.IdBased;
35 import org.thingsboard.server.common.data.id.TenantId; 35 import org.thingsboard.server.common.data.id.TenantId;
36 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;  
37 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; 36 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  37 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
38 import org.thingsboard.server.common.data.page.PageData; 38 import org.thingsboard.server.common.data.page.PageData;
39 import org.thingsboard.server.common.data.page.PageLink; 39 import org.thingsboard.server.common.data.page.PageLink;
40 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 40 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper { @@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper {
93 93
94 private final Lock userCreationLock = new ReentrantLock(); 94 private final Lock userCreationLock = new ReentrantLock();
95 95
96 - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { 96 + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) {
97 97
98 - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); 98 + OAuth2MapperConfig config = registration.getMapperConfig();
99 99
100 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); 100 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
101 101
@@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper { @@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper {
139 } 139 }
140 } 140 }
141 141
142 - if (clientRegistration.getAdditionalInfo() != null &&  
143 - clientRegistration.getAdditionalInfo().has("providerName")) {  
144 - additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); 142 + if (registration.getAdditionalInfo() != null &&
  143 + registration.getAdditionalInfo().has("providerName")) {
  144 + additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText());
145 } 145 }
146 146
147 user.setAdditionalInfo(additionalInfo); 147 user.setAdditionalInfo(additionalInfo);
@@ -18,8 +18,8 @@ package org.thingsboard.server.service.security.auth.oauth2; @@ -18,8 +18,8 @@ package org.thingsboard.server.service.security.auth.oauth2;
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 19 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
20 import org.springframework.stereotype.Service; 20 import org.springframework.stereotype.Service;
21 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;  
22 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; 21 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  22 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
23 import org.thingsboard.server.dao.oauth2.OAuth2User; 23 import org.thingsboard.server.dao.oauth2.OAuth2User;
24 import org.thingsboard.server.service.security.model.SecurityUser; 24 import org.thingsboard.server.service.security.model.SecurityUser;
25 25
@@ -30,12 +30,12 @@ import java.util.Map; @@ -30,12 +30,12 @@ import java.util.Map;
30 public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { 30 public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
31 31
32 @Override 32 @Override
33 - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {  
34 - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); 33 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  34 + OAuth2MapperConfig config = registration.getMapperConfig();
35 Map<String, Object> attributes = token.getPrincipal().getAttributes(); 35 Map<String, Object> attributes = token.getPrincipal().getAttributes();
36 String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); 36 String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
37 OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); 37 OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
38 38
39 - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); 39 + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
40 } 40 }
41 } 41 }
@@ -23,9 +23,9 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic @@ -23,9 +23,9 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
23 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
24 import org.springframework.util.StringUtils; 24 import org.springframework.util.StringUtils;
25 import org.springframework.web.client.RestTemplate; 25 import org.springframework.web.client.RestTemplate;
26 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;  
27 import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; 26 import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
28 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; 27 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  28 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
29 import org.thingsboard.server.dao.oauth2.OAuth2User; 29 import org.thingsboard.server.dao.oauth2.OAuth2User;
30 import org.thingsboard.server.service.security.model.SecurityUser; 30 import org.thingsboard.server.service.security.model.SecurityUser;
31 31
@@ -39,10 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme @@ -39,10 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
39 private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); 39 private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
40 40
41 @Override 41 @Override
42 - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {  
43 - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); 42 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  43 + OAuth2MapperConfig config = registration.getMapperConfig();
44 OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); 44 OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
45 - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); 45 + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
46 } 46 }
47 47
48 private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { 48 private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {
@@ -23,8 +23,8 @@ import org.springframework.boot.web.client.RestTemplateBuilder; @@ -23,8 +23,8 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
23 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 23 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.springframework.web.client.RestTemplate; 25 import org.springframework.web.client.RestTemplate;
26 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;  
27 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; 26 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  27 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
28 import org.thingsboard.server.dao.oauth2.OAuth2Configuration; 28 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
29 import org.thingsboard.server.dao.oauth2.OAuth2User; 29 import org.thingsboard.server.dao.oauth2.OAuth2User;
30 import org.thingsboard.server.service.security.model.SecurityUser; 30 import org.thingsboard.server.service.security.model.SecurityUser;
@@ -46,13 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme @@ -46,13 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
46 private OAuth2Configuration oAuth2Configuration; 46 private OAuth2Configuration oAuth2Configuration;
47 47
48 @Override 48 @Override
49 - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {  
50 - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); 49 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  50 + OAuth2MapperConfig config = registration.getMapperConfig();
51 Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); 51 Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
52 String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); 52 String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
53 Map<String, Object> attributes = token.getPrincipal().getAttributes(); 53 Map<String, Object> attributes = token.getPrincipal().getAttributes();
54 OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); 54 OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
55 - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); 55 + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration);
56 } 56 }
57 57
58 private synchronized String getEmail(String emailUrl, String oauth2Token) { 58 private synchronized String getEmail(String emailUrl, String oauth2Token) {
@@ -16,9 +16,10 @@ @@ -16,9 +16,10 @@
16 package org.thingsboard.server.service.security.auth.oauth2; 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 18 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
19 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; 19 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
  20 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
20 import org.thingsboard.server.service.security.model.SecurityUser; 21 import org.thingsboard.server.service.security.model.SecurityUser;
21 22
22 public interface OAuth2ClientMapper { 23 public interface OAuth2ClientMapper {
23 - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); 24 + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration);
24 } 25 }
@@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2; @@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2;
18 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20 import org.springframework.security.core.AuthenticationException; 20 import org.springframework.security.core.AuthenticationException;
  21 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
21 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 22 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
22 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
  24 +import org.thingsboard.server.common.data.StringUtils;
23 import org.thingsboard.server.common.data.id.CustomerId; 25 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
@@ -51,9 +53,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF @@ -51,9 +53,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
51 public void onAuthenticationFailure(HttpServletRequest request, 53 public void onAuthenticationFailure(HttpServletRequest request,
52 HttpServletResponse response, AuthenticationException exception) 54 HttpServletResponse response, AuthenticationException exception)
53 throws IOException, ServletException { 55 throws IOException, ServletException {
54 - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); 56 + String baseUrl;
  57 + String errorPrefix;
  58 + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request);
  59 + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME);
  60 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  61 + baseUrl = callbackUrlScheme + ":";
  62 + errorPrefix = "/?error=";
  63 + } else {
  64 + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  65 + errorPrefix = "/login?loginError=";
  66 + }
55 httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); 67 httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
56 - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + 68 + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
57 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); 69 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
58 } 70 }
59 } 71 }
@@ -20,12 +20,14 @@ import org.springframework.security.core.Authentication; @@ -20,12 +20,14 @@ import org.springframework.security.core.Authentication;
20 import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; 20 import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
21 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; 21 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
  23 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
23 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 24 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
24 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.data.StringUtils;
25 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.EntityId; 28 import org.thingsboard.server.common.data.id.EntityId;
27 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
28 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; 30 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
29 import org.thingsboard.server.common.data.security.model.JwtToken; 31 import org.thingsboard.server.common.data.security.model.JwtToken;
30 import org.thingsboard.server.dao.oauth2.OAuth2Service; 32 import org.thingsboard.server.dao.oauth2.OAuth2Service;
31 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 33 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
@@ -72,17 +74,24 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS @@ -72,17 +74,24 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
72 public void onAuthenticationSuccess(HttpServletRequest request, 74 public void onAuthenticationSuccess(HttpServletRequest request,
73 HttpServletResponse response, 75 HttpServletResponse response,
74 Authentication authentication) throws IOException { 76 Authentication authentication) throws IOException {
75 - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); 77 + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request);
  78 + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME);
  79 + String baseUrl;
  80 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  81 + baseUrl = callbackUrlScheme + ":";
  82 + } else {
  83 + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  84 + }
76 try { 85 try {
77 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; 86 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
78 87
79 - OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId())); 88 + OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId()));
80 OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( 89 OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
81 token.getAuthorizedClientRegistrationId(), 90 token.getAuthorizedClientRegistrationId(),
82 token.getPrincipal().getName()); 91 token.getPrincipal().getName());
83 - OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); 92 + OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType());
84 SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), 93 SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
85 - clientRegistration); 94 + registration);
86 95
87 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); 96 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
88 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); 97 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
@@ -91,7 +100,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS @@ -91,7 +100,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
91 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); 100 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
92 } catch (Exception e) { 101 } catch (Exception e) {
93 clearAuthenticationAttributes(request, response); 102 clearAuthenticationAttributes(request, response);
94 - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + 103 + String errorPrefix;
  104 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  105 + errorPrefix = "/?error=";
  106 + } else {
  107 + errorPrefix = "/login?loginError=";
  108 + }
  109 + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
95 URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); 110 URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
96 } 111 }
97 } 112 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth2;
  17 +
  18 +public interface TbOAuth2ParameterNames {
  19 +
  20 + String CALLBACK_URL_SCHEME = "callback_url_scheme";
  21 +
  22 +}
@@ -656,7 +656,7 @@ transport: @@ -656,7 +656,7 @@ transport:
656 bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" 656 bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
657 bind_port: "${LWM2M_BIND_PORT_BS:5687}" 657 bind_port: "${LWM2M_BIND_PORT_BS:5687}"
658 security: 658 security:
659 - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" 659 + bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}"
660 bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" 660 bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
661 # Only for RPK: Public & Private Key. If the keystore file is missing or not working 661 # Only for RPK: Public & Private Key. If the keystore file is missing or not working
662 public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" 662 public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}"
@@ -60,6 +60,53 @@ import java.util.concurrent.ScheduledExecutorService; @@ -60,6 +60,53 @@ import java.util.concurrent.ScheduledExecutorService;
60 @DaoSqlTest 60 @DaoSqlTest
61 public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { 61 public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
62 62
  63 + protected final String TRANSPORT_CONFIGURATION = "{\n" +
  64 + " \"type\": \"LWM2M\",\n" +
  65 + " \"observeAttr\": {\n" +
  66 + " \"keyName\": {\n" +
  67 + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
  68 + " },\n" +
  69 + " \"observe\": [],\n" +
  70 + " \"attribute\": [\n" +
  71 + " ],\n" +
  72 + " \"telemetry\": [\n" +
  73 + " \"/3_1.0/0/9\"\n" +
  74 + " ],\n" +
  75 + " \"attributeLwm2m\": {}\n" +
  76 + " },\n" +
  77 + " \"bootstrap\": {\n" +
  78 + " \"servers\": {\n" +
  79 + " \"binding\": \"U\",\n" +
  80 + " \"shortId\": 123,\n" +
  81 + " \"lifetime\": 300,\n" +
  82 + " \"notifIfDisabled\": true,\n" +
  83 + " \"defaultMinPeriod\": 1\n" +
  84 + " },\n" +
  85 + " \"lwm2mServer\": {\n" +
  86 + " \"host\": \"localhost\",\n" +
  87 + " \"port\": 5686,\n" +
  88 + " \"serverId\": 123,\n" +
  89 + " \"serverPublicKey\": \"\",\n" +
  90 + " \"bootstrapServerIs\": false,\n" +
  91 + " \"clientHoldOffTime\": 1,\n" +
  92 + " \"bootstrapServerAccountTimeout\": 0\n" +
  93 + " },\n" +
  94 + " \"bootstrapServer\": {\n" +
  95 + " \"host\": \"localhost\",\n" +
  96 + " \"port\": 5687,\n" +
  97 + " \"serverId\": 111,\n" +
  98 + " \"securityMode\": \"NO_SEC\",\n" +
  99 + " \"serverPublicKey\": \"\",\n" +
  100 + " \"bootstrapServerIs\": true,\n" +
  101 + " \"clientHoldOffTime\": 1,\n" +
  102 + " \"bootstrapServerAccountTimeout\": 0\n" +
  103 + " }\n" +
  104 + " },\n" +
  105 + " \"clientLwM2mSettings\": {\n" +
  106 + " \"clientOnlyObserveAfterConnect\": 1\n" +
  107 + " }\n" +
  108 + "}";
  109 +
63 protected DeviceProfile deviceProfile; 110 protected DeviceProfile deviceProfile;
64 protected ScheduledExecutorService executor; 111 protected ScheduledExecutorService executor;
65 protected TbTestWebSocketClient wsClient; 112 protected TbTestWebSocketClient wsClient;
@@ -22,6 +22,7 @@ import org.junit.Assert; @@ -22,6 +22,7 @@ import org.junit.Assert;
22 import org.junit.Test; 22 import org.junit.Test;
23 import org.thingsboard.common.util.JacksonUtil; 23 import org.thingsboard.common.util.JacksonUtil;
24 import org.thingsboard.server.common.data.Device; 24 import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
25 import org.thingsboard.server.common.data.query.EntityData; 26 import org.thingsboard.server.common.data.query.EntityData;
26 import org.thingsboard.server.common.data.query.EntityDataPageLink; 27 import org.thingsboard.server.common.data.query.EntityDataPageLink;
27 import org.thingsboard.server.common.data.query.EntityDataQuery; 28 import org.thingsboard.server.common.data.query.EntityDataQuery;
@@ -36,7 +37,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; @@ -36,7 +37,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
36 import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; 37 import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
37 import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; 38 import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
38 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; 39 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
39 -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;  
40 40
41 import java.util.Collections; 41 import java.util.Collections;
42 import java.util.List; 42 import java.util.List;
@@ -46,60 +46,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -46,60 +46,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
46 46
47 public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { 47 public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
48 48
49 - protected final String TRANSPORT_CONFIGURATION = "{\n" +  
50 - " \"type\": \"LWM2M\",\n" +  
51 - " \"observeAttr\": {\n" +  
52 - " \"keyName\": {\n" +  
53 - " \"/3_1.0/0/9\": \"batteryLevel\"\n" +  
54 - " },\n" +  
55 - " \"observe\": [],\n" +  
56 - " \"attribute\": [\n" +  
57 - " ],\n" +  
58 - " \"telemetry\": [\n" +  
59 - " \"/3_1.0/0/9\"\n" +  
60 - " ],\n" +  
61 - " \"attributeLwm2m\": {}\n" +  
62 - " },\n" +  
63 - " \"bootstrap\": {\n" +  
64 - " \"servers\": {\n" +  
65 - " \"binding\": \"UQ\",\n" +  
66 - " \"shortId\": 123,\n" +  
67 - " \"lifetime\": 300,\n" +  
68 - " \"notifIfDisabled\": true,\n" +  
69 - " \"defaultMinPeriod\": 1\n" +  
70 - " },\n" +  
71 - " \"lwm2mServer\": {\n" +  
72 - " \"host\": \"localhost\",\n" +  
73 - " \"port\": 5685,\n" +  
74 - " \"serverId\": 123,\n" +  
75 - " \"securityMode\": \"NO_SEC\",\n" +  
76 - " \"serverPublicKey\": \"\",\n" +  
77 - " \"bootstrapServerIs\": false,\n" +  
78 - " \"clientHoldOffTime\": 1,\n" +  
79 - " \"bootstrapServerAccountTimeout\": 0\n" +  
80 - " },\n" +  
81 - " \"bootstrapServer\": {\n" +  
82 - " \"host\": \"localhost\",\n" +  
83 - " \"port\": 5687,\n" +  
84 - " \"serverId\": 111,\n" +  
85 - " \"securityMode\": \"NO_SEC\",\n" +  
86 - " \"serverPublicKey\": \"\",\n" +  
87 - " \"bootstrapServerIs\": true,\n" +  
88 - " \"clientHoldOffTime\": 1,\n" +  
89 - " \"bootstrapServerAccountTimeout\": 0\n" +  
90 - " }\n" +  
91 - " },\n" +  
92 - " \"clientLwM2mSettings\": {\n" +  
93 - " \"clientOnlyObserveAfterConnect\": 1\n" +  
94 - " }\n" +  
95 - "}";  
96 -  
97 - private final int port = 5685;  
98 - private final Security security = noSec("coap://localhost:" + port, 123);  
99 - private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); 49 + private final int PORT = 5685;
  50 + private final Security SECURITY = noSec("coap://localhost:" + PORT, 123);
  51 + private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
  52 + private final String ENDPOINT = "deviceAEndpoint";
100 53
101 @NotNull 54 @NotNull
102 - private Device createDevice(String deviceAEndpoint) throws Exception { 55 + private Device createDevice() throws Exception {
103 Device device = new Device(); 56 Device device = new Device();
104 device.setName("Device A"); 57 device.setName("Device A");
105 device.setDeviceProfileId(deviceProfile.getId()); 58 device.setDeviceProfileId(deviceProfile.getId());
@@ -114,7 +67,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -114,7 +67,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
114 67
115 LwM2MCredentials noSecCredentials = new LwM2MCredentials(); 68 LwM2MCredentials noSecCredentials = new LwM2MCredentials();
116 NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); 69 NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
117 - clientCredentials.setEndpoint(deviceAEndpoint); 70 + clientCredentials.setEndpoint(ENDPOINT);
118 noSecCredentials.setClient(clientCredentials); 71 noSecCredentials.setClient(clientCredentials);
119 deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); 72 deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials));
120 doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); 73 doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
@@ -125,9 +78,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -125,9 +78,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
125 public void testConnectAndObserveTelemetry() throws Exception { 78 public void testConnectAndObserveTelemetry() throws Exception {
126 createDeviceProfile(TRANSPORT_CONFIGURATION); 79 createDeviceProfile(TRANSPORT_CONFIGURATION);
127 80
128 - String deviceAEndpoint = "deviceAEndpoint";  
129 -  
130 - Device device = createDevice(deviceAEndpoint); 81 + Device device = createDevice();
131 82
132 SingleEntityFilter sef = new SingleEntityFilter(); 83 SingleEntityFilter sef = new SingleEntityFilter();
133 sef.setSingleEntity(device.getId()); 84 sef.setSingleEntity(device.getId());
@@ -144,8 +95,8 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -144,8 +95,8 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
144 wsClient.waitForReply(); 95 wsClient.waitForReply();
145 96
146 wsClient.registerWaitForUpdate(); 97 wsClient.registerWaitForUpdate();
147 - LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint);  
148 - client.init(security, coapConfig); 98 + LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT);
  99 + client.init(SECURITY, COAP_CONFIG);
149 String msg = wsClient.waitForUpdate(); 100 String msg = wsClient.waitForUpdate();
150 101
151 EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); 102 EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
@@ -47,54 +47,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -47,54 +47,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
47 47
48 public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { 48 public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
49 49
50 - protected final String TRANSPORT_CONFIGURATION = "{\n" +  
51 - " \"type\": \"LWM2M\",\n" +  
52 - " \"observeAttr\": {\n" +  
53 - " \"keyName\": {\n" +  
54 - " \"/3_1.0/0/9\": \"batteryLevel\"\n" +  
55 - " },\n" +  
56 - " \"observe\": [],\n" +  
57 - " \"attribute\": [\n" +  
58 - " ],\n" +  
59 - " \"telemetry\": [\n" +  
60 - " \"/3_1.0/0/9\"\n" +  
61 - " ],\n" +  
62 - " \"attributeLwm2m\": {}\n" +  
63 - " },\n" +  
64 - " \"bootstrap\": {\n" +  
65 - " \"servers\": {\n" +  
66 - " \"binding\": \"UQ\",\n" +  
67 - " \"shortId\": 123,\n" +  
68 - " \"lifetime\": 300,\n" +  
69 - " \"notifIfDisabled\": true,\n" +  
70 - " \"defaultMinPeriod\": 1\n" +  
71 - " },\n" +  
72 - " \"lwm2mServer\": {\n" +  
73 - " \"host\": \"localhost\",\n" +  
74 - " \"port\": 5686,\n" +  
75 - " \"serverId\": 123,\n" +  
76 - " \"serverPublicKey\": \"\",\n" +  
77 - " \"bootstrapServerIs\": false,\n" +  
78 - " \"clientHoldOffTime\": 1,\n" +  
79 - " \"bootstrapServerAccountTimeout\": 0\n" +  
80 - " },\n" +  
81 - " \"bootstrapServer\": {\n" +  
82 - " \"host\": \"localhost\",\n" +  
83 - " \"port\": 5687,\n" +  
84 - " \"serverId\": 111,\n" +  
85 - " \"securityMode\": \"NO_SEC\",\n" +  
86 - " \"serverPublicKey\": \"\",\n" +  
87 - " \"bootstrapServerIs\": true,\n" +  
88 - " \"clientHoldOffTime\": 1,\n" +  
89 - " \"bootstrapServerAccountTimeout\": 0\n" +  
90 - " }\n" +  
91 - " },\n" +  
92 - " \"clientLwM2mSettings\": {\n" +  
93 - " \"clientOnlyObserveAfterConnect\": 1\n" +  
94 - " }\n" +  
95 - "}";  
96 -  
97 -  
98 private final int port = 5686; 50 private final int port = 5686;
99 private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); 51 private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port));
100 private final String endpoint = "deviceAEndpoint"; 52 private final String endpoint = "deviceAEndpoint";
@@ -17,40 +17,37 @@ package org.thingsboard.server.transport.lwm2m.client; @@ -17,40 +17,37 @@ package org.thingsboard.server.transport.lwm2m.client;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.network.CoapEndpoint;
20 import org.eclipse.californium.core.network.config.NetworkConfig; 21 import org.eclipse.californium.core.network.config.NetworkConfig;
21 -import org.eclipse.californium.elements.Connector; 22 +import org.eclipse.californium.core.observe.ObservationStore;
22 import org.eclipse.californium.scandium.DTLSConnector; 23 import org.eclipse.californium.scandium.DTLSConnector;
23 import org.eclipse.californium.scandium.config.DtlsConnectorConfig; 24 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
24 -import org.eclipse.californium.scandium.dtls.ClientHandshaker;  
25 -import org.eclipse.californium.scandium.dtls.DTLSSession;  
26 -import org.eclipse.californium.scandium.dtls.HandshakeException;  
27 -import org.eclipse.californium.scandium.dtls.Handshaker;  
28 -import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker;  
29 -import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker;  
30 -import org.eclipse.californium.scandium.dtls.ServerHandshaker;  
31 -import org.eclipse.californium.scandium.dtls.SessionAdapter;  
32 import org.eclipse.leshan.client.californium.LeshanClient; 25 import org.eclipse.leshan.client.californium.LeshanClient;
33 import org.eclipse.leshan.client.californium.LeshanClientBuilder; 26 import org.eclipse.leshan.client.californium.LeshanClientBuilder;
34 import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; 27 import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
35 import org.eclipse.leshan.client.object.Security; 28 import org.eclipse.leshan.client.object.Security;
36 import org.eclipse.leshan.client.object.Server; 29 import org.eclipse.leshan.client.object.Server;
37 import org.eclipse.leshan.client.observer.LwM2mClientObserver; 30 import org.eclipse.leshan.client.observer.LwM2mClientObserver;
  31 +import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
38 import org.eclipse.leshan.client.resource.ObjectsInitializer; 32 import org.eclipse.leshan.client.resource.ObjectsInitializer;
39 import org.eclipse.leshan.client.servers.ServerIdentity; 33 import org.eclipse.leshan.client.servers.ServerIdentity;
  34 +import org.eclipse.leshan.core.LwM2mId;
40 import org.eclipse.leshan.core.ResponseCode; 35 import org.eclipse.leshan.core.ResponseCode;
41 -import org.eclipse.leshan.core.californium.DefaultEndpointFactory; 36 +import org.eclipse.leshan.core.californium.EndpointFactory;
  37 +import org.eclipse.leshan.core.model.InvalidDDFFileException;
42 import org.eclipse.leshan.core.model.LwM2mModel; 38 import org.eclipse.leshan.core.model.LwM2mModel;
43 import org.eclipse.leshan.core.model.ObjectLoader; 39 import org.eclipse.leshan.core.model.ObjectLoader;
44 import org.eclipse.leshan.core.model.ObjectModel; 40 import org.eclipse.leshan.core.model.ObjectModel;
45 import org.eclipse.leshan.core.model.StaticModel; 41 import org.eclipse.leshan.core.model.StaticModel;
46 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; 42 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
47 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; 43 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
48 -import org.eclipse.leshan.core.request.BindingMode;  
49 import org.eclipse.leshan.core.request.BootstrapRequest; 44 import org.eclipse.leshan.core.request.BootstrapRequest;
50 import org.eclipse.leshan.core.request.DeregisterRequest; 45 import org.eclipse.leshan.core.request.DeregisterRequest;
51 import org.eclipse.leshan.core.request.RegisterRequest; 46 import org.eclipse.leshan.core.request.RegisterRequest;
52 import org.eclipse.leshan.core.request.UpdateRequest; 47 import org.eclipse.leshan.core.request.UpdateRequest;
53 48
  49 +import java.io.IOException;
  50 +import java.net.InetSocketAddress;
54 import java.util.ArrayList; 51 import java.util.ArrayList;
55 import java.util.List; 52 import java.util.List;
56 import java.util.concurrent.ScheduledExecutorService; 53 import java.util.concurrent.ScheduledExecutorService;
@@ -67,7 +64,7 @@ public class LwM2MTestClient { @@ -67,7 +64,7 @@ public class LwM2MTestClient {
67 private final String endpoint; 64 private final String endpoint;
68 private LeshanClient client; 65 private LeshanClient client;
69 66
70 - public void init(Security security, NetworkConfig coapConfig) { 67 + public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException {
71 String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; 68 String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"};
72 List<ObjectModel> models = new ArrayList<>(); 69 List<ObjectModel> models = new ArrayList<>();
73 for (String resourceName : resources) { 70 for (String resourceName : resources) {
@@ -76,82 +73,51 @@ public class LwM2MTestClient { @@ -76,82 +73,51 @@ public class LwM2MTestClient {
76 LwM2mModel model = new StaticModel(models); 73 LwM2mModel model = new StaticModel(models);
77 ObjectsInitializer initializer = new ObjectsInitializer(model); 74 ObjectsInitializer initializer = new ObjectsInitializer(model);
78 initializer.setInstancesForObject(SECURITY, security); 75 initializer.setInstancesForObject(SECURITY, security);
79 - initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); 76 + initializer.setInstancesForObject(SERVER, new Server(123, 300));
80 initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); 77 initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
  78 + initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class);
81 79
82 DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); 80 DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
83 dtlsConfig.setRecommendedCipherSuitesOnly(true); 81 dtlsConfig.setRecommendedCipherSuitesOnly(true);
  82 + dtlsConfig.setClientOnly();
84 83
85 DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); 84 DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
86 engineFactory.setReconnectOnUpdate(false); 85 engineFactory.setReconnectOnUpdate(false);
87 engineFactory.setResumeOnConnect(true); 86 engineFactory.setResumeOnConnect(true);
88 87
89 - DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) { 88 + EndpointFactory endpointFactory = new EndpointFactory() {
  89 +
90 @Override 90 @Override
91 - protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {  
92 -  
93 - return new DTLSConnector(dtlsConfig) {  
94 - @Override  
95 - protected void onInitializeHandshaker(Handshaker handshaker) {  
96 - handshaker.addSessionListener(new SessionAdapter() {  
97 -  
98 - @Override  
99 - public void handshakeStarted(Handshaker handshaker) throws HandshakeException {  
100 - if (handshaker instanceof ServerHandshaker) {  
101 - log.info("DTLS Full Handshake initiated by server : STARTED ...");  
102 - } else if (handshaker instanceof ResumingServerHandshaker) {  
103 - log.info("DTLS abbreviated Handshake initiated by server : STARTED ...");  
104 - } else if (handshaker instanceof ClientHandshaker) {  
105 - log.info("DTLS Full Handshake initiated by client : STARTED ...");  
106 - } else if (handshaker instanceof ResumingClientHandshaker) {  
107 - log.info("DTLS abbreviated Handshake initiated by client : STARTED ...");  
108 - }  
109 - }  
110 -  
111 - @Override  
112 - public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession)  
113 - throws HandshakeException {  
114 - if (handshaker instanceof ServerHandshaker) {  
115 - log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker);  
116 - } else if (handshaker instanceof ResumingServerHandshaker) {  
117 - log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker);  
118 - } else if (handshaker instanceof ClientHandshaker) {  
119 - log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker);  
120 - } else if (handshaker instanceof ResumingClientHandshaker) {  
121 - log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker);  
122 - }  
123 - }  
124 -  
125 - @Override  
126 - public void handshakeFailed(Handshaker handshaker, Throwable error) {  
127 - /** get cause */  
128 - String cause;  
129 - if (error != null) {  
130 - if (error.getMessage() != null) {  
131 - cause = error.getMessage();  
132 - } else {  
133 - cause = error.getClass().getName();  
134 - }  
135 - } else {  
136 - cause = "unknown cause";  
137 - }  
138 -  
139 - if (handshaker instanceof ServerHandshaker) {  
140 - log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause);  
141 - } else if (handshaker instanceof ResumingServerHandshaker) {  
142 - log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause);  
143 - } else if (handshaker instanceof ClientHandshaker) {  
144 - log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause);  
145 - } else if (handshaker instanceof ResumingClientHandshaker) {  
146 - log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause);  
147 - }  
148 - }  
149 - });  
150 - }  
151 - }; 91 + public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig,
  92 + ObservationStore store) {
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setInetSocketAddress(address);
  95 + builder.setNetworkConfig(coapConfig);
  96 + return builder.build();
  97 + }
  98 +
  99 + @Override
  100 + public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig,
  101 + ObservationStore store) {
  102 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  103 + DtlsConnectorConfig.Builder dtlsConfigBuilder = new DtlsConnectorConfig.Builder(dtlsConfig);
  104 +
  105 + // tricks to be able to change psk information on the fly
  106 +// AdvancedPskStore pskStore = dtlsConfig.getAdvancedPskStore();
  107 +// if (pskStore != null) {
  108 +// PskPublicInformation identity = pskStore.getIdentity(null, null);
  109 +// SecretKey key = pskStore
  110 +// .requestPskSecretResult(ConnectionId.EMPTY, null, identity, null, null, null).getSecret();
  111 +// singlePSKStore = new SinglePSKStore(identity, key);
  112 +// dtlsConfigBuilder.setAdvancedPskStore(singlePSKStore);
  113 +// }
  114 + builder.setConnector(new DTLSConnector(dtlsConfigBuilder.build()));
  115 + builder.setNetworkConfig(coapConfig);
  116 + return builder.build();
152 } 117 }
153 }; 118 };
154 119
  120 +
155 LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); 121 LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
156 builder.setLocalAddress("0.0.0.0", 11000); 122 builder.setLocalAddress("0.0.0.0", 11000);
157 builder.setObjects(initializer.createAll()); 123 builder.setObjects(initializer.createAll());
@@ -246,6 +212,11 @@ public class LwM2MTestClient { @@ -246,6 +212,11 @@ public class LwM2MTestClient {
246 public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { 212 public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
247 log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); 213 log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
248 } 214 }
  215 +
  216 + @Override
  217 + public void onUnexpectedError(Throwable unexpectedError) {
  218 +
  219 + }
249 }; 220 };
250 this.client.addObserver(observer); 221 this.client.addObserver(observer);
251 222
@@ -97,7 +97,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl @@ -97,7 +97,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
97 } 97 }
98 98
99 @Override 99 @Override
100 - public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) { 100 + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) {
101 log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); 101 log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
102 102
103 switch (resourceid) { 103 switch (resourceid) {
@@ -112,7 +112,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl @@ -112,7 +112,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
112 fireResourcesChange(resourceid); 112 fireResourcesChange(resourceid);
113 return WriteResponse.success(); 113 return WriteResponse.success();
114 default: 114 default:
115 - return super.write(identity, resourceid, value); 115 + return super.write(identity, replace, resourceid, value);
116 } 116 }
117 } 117 }
118 118
@@ -16,20 +16,31 @@ @@ -16,20 +16,31 @@
16 package org.thingsboard.server.dao.oauth2; 16 package org.thingsboard.server.dao.oauth2;
17 17
18 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 18 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
19 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;  
20 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; 19 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
  20 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
  21 +import org.thingsboard.server.common.data.oauth2.PlatformType;
  22 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
  23 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
21 24
22 import java.util.List; 25 import java.util.List;
23 import java.util.UUID; 26 import java.util.UUID;
24 27
25 public interface OAuth2Service { 28 public interface OAuth2Service {
26 - List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName); 29 + List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
27 30
  31 + @Deprecated
28 void saveOAuth2Params(OAuth2ClientsParams oauth2Params); 32 void saveOAuth2Params(OAuth2ClientsParams oauth2Params);
29 33
  34 + @Deprecated
30 OAuth2ClientsParams findOAuth2Params(); 35 OAuth2ClientsParams findOAuth2Params();
31 36
32 - OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id); 37 + void saveOAuth2Info(OAuth2Info oauth2Info);
33 38
34 - List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos(); 39 + OAuth2Info findOAuth2Info();
  40 +
  41 + OAuth2Registration findRegistration(UUID id);
  42 +
  43 + List<OAuth2Registration> findAllRegistrations();
  44 +
  45 + String findCallbackUrlScheme(UUID registrationId, String pkgName);
35 } 46 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2DomainId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2DomainId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2DomainId fromString(String oauth2DomainId) {
  31 + return new OAuth2DomainId(UUID.fromString(oauth2DomainId));
  32 + }
  33 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2MobileId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2MobileId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2MobileId fromString(String oauth2MobileId) {
  31 + return new OAuth2MobileId(UUID.fromString(oauth2MobileId));
  32 + }
  33 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2ParamsId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2ParamsId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2ParamsId fromString(String oauth2ParamsId) {
  31 + return new OAuth2ParamsId(UUID.fromString(oauth2ParamsId));
  32 + }
  33 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2RegistrationId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2RegistrationId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2RegistrationId fromString(String oauth2RegistrationId) {
  31 + return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId));
  32 + }
  33 +}
common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationId.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java
@@ -13,13 +13,15 @@ @@ -13,13 +13,15 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.id; 16 +package org.thingsboard.server.common.data.id.deprecated;
17 17
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import org.thingsboard.server.common.data.id.UUIDBased;
20 21
21 import java.util.UUID; 22 import java.util.UUID;
22 23
  24 +@Deprecated
23 public class OAuth2ClientRegistrationId extends UUIDBased { 25 public class OAuth2ClientRegistrationId extends UUIDBased {
24 26
25 @JsonCreator 27 @JsonCreator
common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationInfoId.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java
@@ -13,13 +13,15 @@ @@ -13,13 +13,15 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.id; 16 +package org.thingsboard.server.common.data.id.deprecated;
17 17
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import org.thingsboard.server.common.data.id.UUIDBased;
20 21
21 import java.util.UUID; 22 import java.util.UUID;
22 23
  24 +@Deprecated
23 public class OAuth2ClientRegistrationInfoId extends UUIDBased { 25 public class OAuth2ClientRegistrationInfoId extends UUIDBased {
24 26
25 @JsonCreator 27 @JsonCreator
@@ -20,10 +20,8 @@ import lombok.EqualsAndHashCode; @@ -20,10 +20,8 @@ import lombok.EqualsAndHashCode;
20 import lombok.NoArgsConstructor; 20 import lombok.NoArgsConstructor;
21 import lombok.ToString; 21 import lombok.ToString;
22 import org.thingsboard.server.common.data.HasName; 22 import org.thingsboard.server.common.data.HasName;
23 -import org.thingsboard.server.common.data.HasTenantId;  
24 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 23 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; 24 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
26 -import org.thingsboard.server.common.data.id.TenantId;  
27 25
28 import java.util.List; 26 import java.util.List;
29 27
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2DomainId;
  24 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Domain extends BaseData<OAuth2DomainId> {
  31 +
  32 + private OAuth2ParamsId oauth2ParamsId;
  33 + private String domainName;
  34 + private SchemeType domainScheme;
  35 +
  36 + public OAuth2Domain(OAuth2Domain domain) {
  37 + super(domain);
  38 + this.oauth2ParamsId = domain.oauth2ParamsId;
  39 + this.domainName = domain.domainName;
  40 + this.domainScheme = domain.domainScheme;
  41 + }
  42 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class OAuth2DomainInfo {
  32 + private SchemeType scheme;
  33 + private String name;
  34 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +import java.util.List;
  21 +
  22 +@EqualsAndHashCode
  23 +@Data
  24 +@ToString
  25 +@Builder(toBuilder = true)
  26 +@NoArgsConstructor
  27 +@AllArgsConstructor
  28 +public class OAuth2Info {
  29 + private boolean enabled;
  30 + private List<OAuth2ParamsInfo> oauth2ParamsInfos;
  31 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2MobileId;
  24 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Mobile extends BaseData<OAuth2MobileId> {
  31 +
  32 + private OAuth2ParamsId oauth2ParamsId;
  33 + private String pkgName;
  34 + private String callbackUrlScheme;
  35 +
  36 + public OAuth2Mobile(OAuth2Mobile mobile) {
  37 + super(mobile);
  38 + this.oauth2ParamsId = mobile.oauth2ParamsId;
  39 + this.pkgName = mobile.pkgName;
  40 + this.callbackUrlScheme = mobile.callbackUrlScheme;
  41 + }
  42 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class OAuth2MobileInfo {
  32 + private String pkgName;
  33 + private String callbackUrlScheme;
  34 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Params extends BaseData<OAuth2ParamsId> {
  31 +
  32 + private boolean enabled;
  33 + private TenantId tenantId;
  34 +
  35 + public OAuth2Params(OAuth2Params oauth2Params) {
  36 + super(oauth2Params);
  37 + this.enabled = oauth2Params.enabled;
  38 + this.tenantId = oauth2Params.tenantId;
  39 + }
  40 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +import java.util.List;
  26 +
  27 +@EqualsAndHashCode
  28 +@Data
  29 +@ToString
  30 +@Builder(toBuilder = true)
  31 +@NoArgsConstructor
  32 +@AllArgsConstructor
  33 +public class OAuth2ParamsInfo {
  34 +
  35 + private List<OAuth2DomainInfo> domainInfos;
  36 + private List<OAuth2MobileInfo> mobileInfos;
  37 + private List<OAuth2RegistrationInfo> clientRegistrations;
  38 +
  39 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.NoArgsConstructor;
  22 +import lombok.ToString;
  23 +import org.thingsboard.server.common.data.HasName;
  24 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  25 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  26 +import org.thingsboard.server.common.data.id.OAuth2RegistrationId;
  27 +
  28 +import java.util.List;
  29 +
  30 +@EqualsAndHashCode(callSuper = true)
  31 +@Data
  32 +@ToString(exclude = {"clientSecret"})
  33 +@NoArgsConstructor
  34 +public class OAuth2Registration extends SearchTextBasedWithAdditionalInfo<OAuth2RegistrationId> implements HasName {
  35 +
  36 + private OAuth2ParamsId oauth2ParamsId;
  37 + private OAuth2MapperConfig mapperConfig;
  38 + private String clientId;
  39 + private String clientSecret;
  40 + private String authorizationUri;
  41 + private String accessTokenUri;
  42 + private List<String> scope;
  43 + private String userInfoUri;
  44 + private String userNameAttributeName;
  45 + private String jwkSetUri;
  46 + private String clientAuthenticationMethod;
  47 + private String loginButtonLabel;
  48 + private String loginButtonIcon;
  49 + private List<PlatformType> platforms;
  50 +
  51 + public OAuth2Registration(OAuth2Registration registration) {
  52 + super(registration);
  53 + this.oauth2ParamsId = registration.oauth2ParamsId;
  54 + this.mapperConfig = registration.mapperConfig;
  55 + this.clientId = registration.clientId;
  56 + this.clientSecret = registration.clientSecret;
  57 + this.authorizationUri = registration.authorizationUri;
  58 + this.accessTokenUri = registration.accessTokenUri;
  59 + this.scope = registration.scope;
  60 + this.userInfoUri = registration.userInfoUri;
  61 + this.userNameAttributeName = registration.userNameAttributeName;
  62 + this.jwkSetUri = registration.jwkSetUri;
  63 + this.clientAuthenticationMethod = registration.clientAuthenticationMethod;
  64 + this.loginButtonLabel = registration.loginButtonLabel;
  65 + this.loginButtonIcon = registration.loginButtonIcon;
  66 + this.platforms = registration.platforms;
  67 + }
  68 +
  69 + @Override
  70 + @JsonProperty(access = JsonProperty.Access.READ_ONLY)
  71 + public String getName() {
  72 + return loginButtonLabel;
  73 + }
  74 +
  75 + @Override
  76 + public String getSearchText() {
  77 + return getName();
  78 + }
  79 +}
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java
@@ -17,7 +17,6 @@ package org.thingsboard.server.common.data.oauth2; @@ -17,7 +17,6 @@ package org.thingsboard.server.common.data.oauth2;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.*; 19 import lombok.*;
20 -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;  
21 20
22 import java.util.List; 21 import java.util.List;
23 22
@@ -27,7 +26,7 @@ import java.util.List; @@ -27,7 +26,7 @@ import java.util.List;
27 @NoArgsConstructor 26 @NoArgsConstructor
28 @AllArgsConstructor 27 @AllArgsConstructor
29 @Builder 28 @Builder
30 -public class ClientRegistrationDto { 29 +public class OAuth2RegistrationInfo {
31 private OAuth2MapperConfig mapperConfig; 30 private OAuth2MapperConfig mapperConfig;
32 private String clientId; 31 private String clientId;
33 private String clientSecret; 32 private String clientSecret;
@@ -40,5 +39,6 @@ public class ClientRegistrationDto { @@ -40,5 +39,6 @@ public class ClientRegistrationDto {
40 private String clientAuthenticationMethod; 39 private String clientAuthenticationMethod;
41 private String loginButtonLabel; 40 private String loginButtonLabel;
42 private String loginButtonIcon; 41 private String loginButtonIcon;
  42 + private List<PlatformType> platforms;
43 private JsonNode additionalInfo; 43 private JsonNode additionalInfo;
44 } 44 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +public enum PlatformType {
  19 + WEB, ANDROID, IOS
  20 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.*;
  20 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  21 +
  22 +import java.util.List;
  23 +
  24 +@Deprecated
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString(exclude = {"clientSecret"})
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class ClientRegistrationDto {
  32 + private OAuth2MapperConfig mapperConfig;
  33 + private String clientId;
  34 + private String clientSecret;
  35 + private String authorizationUri;
  36 + private String accessTokenUri;
  37 + private List<String> scope;
  38 + private String userInfoUri;
  39 + private String userNameAttributeName;
  40 + private String jwkSetUri;
  41 + private String clientAuthenticationMethod;
  42 + private String loginButtonLabel;
  43 + private String loginButtonIcon;
  44 + private JsonNode additionalInfo;
  45 +}
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/DomainInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java
@@ -13,10 +13,12 @@ @@ -13,10 +13,12 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import lombok.*; 18 import lombok.*;
  19 +import org.thingsboard.server.common.data.oauth2.SchemeType;
19 20
  21 +@Deprecated
20 @EqualsAndHashCode 22 @EqualsAndHashCode
21 @Data 23 @Data
22 @ToString 24 @ToString
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ExtendedOAuth2ClientRegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java
@@ -13,11 +13,14 @@ @@ -13,11 +13,14 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import lombok.EqualsAndHashCode; 19 import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  21 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
20 22
  23 +@Deprecated
21 @EqualsAndHashCode(callSuper = true) 24 @EqualsAndHashCode(callSuper = true)
22 @Data 25 @Data
23 public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo { 26 public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo {
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistration.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java
@@ -13,16 +13,18 @@ @@ -13,16 +13,18 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import lombok.EqualsAndHashCode; 19 import lombok.EqualsAndHashCode;
20 import lombok.NoArgsConstructor; 20 import lombok.NoArgsConstructor;
21 import lombok.ToString; 21 import lombok.ToString;
22 import org.thingsboard.server.common.data.BaseData; 22 import org.thingsboard.server.common.data.BaseData;
23 -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;  
24 -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; 23 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationId;
  24 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId;
  25 +import org.thingsboard.server.common.data.oauth2.SchemeType;
25 26
  27 +@Deprecated
26 @EqualsAndHashCode(callSuper = true) 28 @EqualsAndHashCode(callSuper = true)
27 @Data 29 @Data
28 @ToString 30 @ToString
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import com.fasterxml.jackson.annotation.JsonProperty; 18 import com.fasterxml.jackson.annotation.JsonProperty;
19 import lombok.Data; 19 import lombok.Data;
@@ -22,10 +22,12 @@ import lombok.NoArgsConstructor; @@ -22,10 +22,12 @@ import lombok.NoArgsConstructor;
22 import lombok.ToString; 22 import lombok.ToString;
23 import org.thingsboard.server.common.data.HasName; 23 import org.thingsboard.server.common.data.HasName;
24 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 24 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; 25 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
26 27
27 import java.util.List; 28 import java.util.List;
28 29
  30 +@Deprecated
29 @EqualsAndHashCode(callSuper = true) 31 @EqualsAndHashCode(callSuper = true)
30 @Data 32 @Data
31 @ToString(exclude = {"clientSecret"}) 33 @ToString(exclude = {"clientSecret"})
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsDomainParams.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java
@@ -13,13 +13,13 @@ @@ -13,13 +13,13 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import lombok.*; 18 import lombok.*;
19 19
20 import java.util.List; 20 import java.util.List;
21 -import java.util.Set;  
22 21
  22 +@Deprecated
23 @EqualsAndHashCode 23 @EqualsAndHashCode
24 @Data 24 @Data
25 @ToString 25 @ToString
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsParams.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java
@@ -13,13 +13,13 @@ @@ -13,13 +13,13 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.common.data.oauth2; 16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 import lombok.*; 18 import lombok.*;
19 19
20 import java.util.List; 20 import java.util.List;
21 -import java.util.Set;  
22 21
  22 +@Deprecated
23 @EqualsAndHashCode 23 @EqualsAndHashCode
24 @Data 24 @Data
25 @ToString 25 @ToString
@@ -310,10 +310,12 @@ message SessionCloseNotificationProto { @@ -310,10 +310,12 @@ message SessionCloseNotificationProto {
310 310
311 message SubscribeToAttributeUpdatesMsg { 311 message SubscribeToAttributeUpdatesMsg {
312 bool unsubscribe = 1; 312 bool unsubscribe = 1;
  313 + SessionType sessionType = 2;
313 } 314 }
314 315
315 message SubscribeToRPCMsg { 316 message SubscribeToRPCMsg {
316 bool unsubscribe = 1; 317 bool unsubscribe = 1;
  318 + SessionType sessionType = 2;
317 } 319 }
318 320
319 message ToDeviceRpcRequestMsg { 321 message ToDeviceRpcRequestMsg {
@@ -483,13 +483,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -483,13 +483,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
483 String title = exchange.getQueryParameter("title"); 483 String title = exchange.getQueryParameter("title");
484 String version = exchange.getQueryParameter("version"); 484 String version = exchange.getQueryParameter("version");
485 if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { 485 if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
  486 + String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();
486 if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { 487 if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) {
487 - String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();  
488 String strChunkSize = exchange.getQueryParameter("size"); 488 String strChunkSize = exchange.getQueryParameter("size");
489 String strChunk = exchange.getQueryParameter("chunk"); 489 String strChunk = exchange.getQueryParameter("chunk");
490 int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); 490 int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
491 int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); 491 int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk);
492 exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); 492 exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk));
  493 + }
  494 + else if (firmwareId != null) {
  495 + sendOtaData(exchange, firmwareId);
493 } else { 496 } else {
494 exchange.respond(CoAP.ResponseCode.BAD_REQUEST); 497 exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
495 } 498 }
@@ -505,6 +508,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -505,6 +508,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
505 } 508 }
506 } 509 }
507 510
  511 + private void sendOtaData(CoapExchange exchange, String firmwareId) {
  512 + Response response = new Response(CoAP.ResponseCode.CONTENT);
  513 + byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId);
  514 + if (fwData != null && fwData.length > 0) {
  515 + response.setPayload(fwData);
  516 + if (exchange.getRequestOptions().getBlock2() != null) {
  517 + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
  518 + boolean moreFlag = fwData.length > chunkSize;
  519 + response.getOptions().setBlock2(chunkSize, moreFlag, 0);
  520 + }
  521 + exchange.respond(response);
  522 + }
  523 + }
  524 +
508 private static class CoapSessionListener implements SessionMsgListener { 525 private static class CoapSessionListener implements SessionMsgListener {
509 526
510 private final CoapTransportResource coapTransportResource; 527 private final CoapTransportResource coapTransportResource;
@@ -572,7 +589,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -572,7 +589,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
572 } 589 }
573 if (msg.getPersisted()) { 590 if (msg.getPersisted()) {
574 RpcStatus status; 591 RpcStatus status;
575 - if (successful) { 592 + if (!successful) {
576 status = RpcStatus.FAILED; 593 status = RpcStatus.FAILED;
577 } else if (msg.getOneway()) { 594 } else if (msg.getOneway()) {
578 status = RpcStatus.SUCCESSFUL; 595 status = RpcStatus.SUCCESSFUL;
@@ -102,15 +102,15 @@ public class LwM2MTransportBootstrapService { @@ -102,15 +102,15 @@ public class LwM2MTransportBootstrapService {
102 builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); 102 builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort());
103 103
104 /** Create CoAP Config */ 104 /** Create CoAP Config */
105 - builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort())); 105 + builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
106 106
107 /** Define model provider (Create Models )*/ 107 /** Define model provider (Create Models )*/
108 108
109 /** Create credentials */ 109 /** Create credentials */
110 this.setServerWithCredentials(builder); 110 this.setServerWithCredentials(builder);
111 111
112 - /** Set securityStore with new ConfigStore */  
113 - builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); 112 +// /** Set securityStore with new ConfigStore */
  113 +// builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
114 114
115 /** SecurityStore */ 115 /** SecurityStore */
116 builder.setSecurityStore(lwM2MBootstrapSecurityStore); 116 builder.setSecurityStore(lwM2MBootstrapSecurityStore);
@@ -69,7 +69,7 @@ public class LwM2MBootstrapConfig { @@ -69,7 +69,7 @@ public class LwM2MBootstrapConfig {
69 server0.lifetime = servers.getLifetime(); 69 server0.lifetime = servers.getLifetime();
70 server0.defaultMinPeriod = servers.getDefaultMinPeriod(); 70 server0.defaultMinPeriod = servers.getDefaultMinPeriod();
71 server0.notifIfDisabled = servers.isNotifIfDisabled(); 71 server0.notifIfDisabled = servers.isNotifIfDisabled();
72 - server0.binding = BindingMode.valueOf(servers.getBinding()); 72 + server0.binding = BindingMode.parse(servers.getBinding());
73 configBs.servers.put(0, server0); 73 configBs.servers.put(0, server0);
74 /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ 74 /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
75 this.bootstrapServer.setBootstrapServerIs(true); 75 this.bootstrapServer.setBootstrapServerIs(true);
@@ -30,7 +30,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; @@ -30,7 +30,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
30 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 30 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
31 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
32 import org.thingsboard.server.gen.transport.TransportProtos; 32 import org.thingsboard.server.gen.transport.TransportProtos;
33 -import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; 33 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
34 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; 34 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
35 import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; 35 import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
36 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; 36 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
@@ -40,7 +40,7 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -40,7 +40,7 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
40 import java.io.IOException; 40 import java.io.IOException;
41 import java.security.GeneralSecurityException; 41 import java.security.GeneralSecurityException;
42 import java.util.Collections; 42 import java.util.Collections;
43 -import java.util.List; 43 +import java.util.Iterator;
44 import java.util.UUID; 44 import java.util.UUID;
45 45
46 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER; 46 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER;
@@ -71,8 +71,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @@ -71,8 +71,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
71 } 71 }
72 72
73 @Override 73 @Override
74 - public List<SecurityInfo> getAllByEndpoint(String endPoint) {  
75 - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); 74 + public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
  75 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
76 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { 76 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
77 /* add value to store from BootstrapJson */ 77 /* add value to store from BootstrapJson */
78 this.setBootstrapConfigScurityInfo(store); 78 this.setBootstrapConfigScurityInfo(store);
@@ -88,7 +88,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @@ -88,7 +88,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
88 } catch (InvalidConfigurationException e) { 88 } catch (InvalidConfigurationException e) {
89 log.error("", e); 89 log.error("", e);
90 } 90 }
91 - return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()); 91 + return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
92 } 92 }
93 } 93 }
94 return null; 94 return null;
@@ -96,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @@ -96,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
96 96
97 @Override 97 @Override
98 public SecurityInfo getByIdentity(String identity) { 98 public SecurityInfo getByIdentity(String identity) {
99 - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); 99 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
100 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { 100 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
101 /* add value to store from BootstrapJson */ 101 /* add value to store from BootstrapJson */
102 this.setBootstrapConfigScurityInfo(store); 102 this.setBootstrapConfigScurityInfo(store);
@@ -113,7 +113,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @@ -113,7 +113,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
113 return null; 113 return null;
114 } 114 }
115 115
116 - private void setBootstrapConfigScurityInfo(EndpointSecurityInfo store) { 116 + private void setBootstrapConfigScurityInfo(TbLwM2MSecurityInfo store) {
117 /* BootstrapConfig */ 117 /* BootstrapConfig */
118 LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); 118 LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
119 if (lwM2MBootstrapConfig != null) { 119 if (lwM2MBootstrapConfig != null) {
@@ -150,7 +150,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @@ -150,7 +150,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
150 } 150 }
151 } 151 }
152 152
153 - private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { 153 + private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) {
154 try { 154 try {
155 LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); 155 LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig();
156 if (lwM2MBootstrapConfig != null) { 156 if (lwM2MBootstrapConfig != null) {
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.transport.lwm2m.bootstrap.secure; 16 package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.core.request.BootstrapRequest;
19 import org.eclipse.leshan.core.request.Identity; 20 import org.eclipse.leshan.core.request.Identity;
20 import org.eclipse.leshan.server.bootstrap.BootstrapSession; 21 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
21 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; 22 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
@@ -25,7 +26,7 @@ import org.eclipse.leshan.server.security.SecurityChecker; @@ -25,7 +26,7 @@ import org.eclipse.leshan.server.security.SecurityChecker;
25 import org.eclipse.leshan.server.security.SecurityInfo; 26 import org.eclipse.leshan.server.security.SecurityInfo;
26 27
27 import java.util.Collections; 28 import java.util.Collections;
28 -import java.util.List; 29 +import java.util.Iterator;
29 30
30 @Slf4j 31 @Slf4j
31 public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { 32 public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
@@ -50,16 +51,17 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession @@ -50,16 +51,17 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
50 } 51 }
51 52
52 @SuppressWarnings("deprecation") 53 @SuppressWarnings("deprecation")
53 - public BootstrapSession begin(String endpoint, Identity clientIdentity) { 54 + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
54 boolean authorized; 55 boolean authorized;
55 if (bsSecurityStore != null) { 56 if (bsSecurityStore != null) {
56 - List<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint); 57 + Iterator<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ?
  58 + Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName());
57 log.info("Bootstrap session started securityInfos: [{}]", securityInfos); 59 log.info("Bootstrap session started securityInfos: [{}]", securityInfos);
58 - authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos); 60 + authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
59 } else { 61 } else {
60 authorized = true; 62 authorized = true;
61 } 63 }
62 - DefaultBootstrapSession session = new DefaultBootstrapSession(endpoint, clientIdentity, authorized); 64 + DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
63 log.info("Bootstrap session started : {}", session); 65 log.info("Bootstrap session started : {}", session);
64 return session; 66 return session;
65 } 67 }