Commit afcadea9c2f6045cd4e8b5e21f0d7f4c32cf36b0

Authored by Sergey Matvienko
2 parents 9ffa50f4 26bc7841

Merge branch 'master' into js-executors-kafka-batches

# Conflicts:
#	msa/js-executor/queue/kafkaTemplate.js
Showing 28 changed files with 3336 additions and 505 deletions

Too many changes to show.

To preserve performance only 28 of 333 files are displayed.

1 { 1 {
2 "title": "Firmware", 2 "title": "Firmware",
  3 + "image": null,
3 "configuration": { 4 "configuration": {
4 "description": "", 5 "description": "",
5 "widgets": { 6 "widgets": {
@@ -222,6 +223,27 @@ @@ -222,6 +223,27 @@
222 "funcBody": null, 223 "funcBody": null,
223 "usePostProcessing": null, 224 "usePostProcessing": null,
224 "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
225 } 247 }
226 ] 248 ]
227 } 249 }
@@ -247,24 +269,24 @@ @@ -247,24 +269,24 @@
247 "name": "Edit firmware", 269 "name": "Edit firmware",
248 "icon": "edit", 270 "icon": "edit",
249 "type": "customPretty", 271 "type": "customPretty",
250 - "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-firmware-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-firmware-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>",  
251 - "customCss": "", 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>",
  273 + "customCss": "form {\n min-width: 300px !important;\n}",
252 "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}",
253 "customResources": [], 275 "customResources": [],
254 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 276 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
255 }, 277 },
256 { 278 {
257 - "name": "Download firware", 279 + "name": "Download firmware",
258 "icon": "file_download", 280 "icon": "file_download",
259 "type": "custom", 281 "type": "custom",
260 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\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 firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(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}",
261 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 283 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
262 }, 284 },
263 { 285 {
264 - "name": "Copy checksum", 286 + "name": "Copy checksum/URL",
265 "icon": "content_copy", 287 "icon": "content_copy",
266 "type": "custom", 288 "type": "custom",
267 - "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}",
268 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 290 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
269 } 291 }
270 ] 292 ]
@@ -996,6 +1018,27 @@ @@ -996,6 +1018,27 @@
996 "funcBody": null, 1018 "funcBody": null,
997 "usePostProcessing": null, 1019 "usePostProcessing": null,
998 "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
999 } 1042 }
1000 ] 1043 ]
1001 } 1044 }
@@ -1021,24 +1064,24 @@ @@ -1021,24 +1064,24 @@
1021 "name": "Edit firmware", 1064 "name": "Edit firmware",
1022 "icon": "edit", 1065 "icon": "edit",
1023 "type": "customPretty", 1066 "type": "customPretty",
1024 - "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 mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-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>",  
1025 - "customCss": "", 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>",
  1068 + "customCss": "form {\n min-width: 300px !important;\n}",
1026 "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}",
1027 "customResources": [], 1070 "customResources": [],
1028 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1071 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1029 }, 1072 },
1030 { 1073 {
1031 - "name": "Download firware", 1074 + "name": "Download firmware",
1032 "icon": "file_download", 1075 "icon": "file_download",
1033 "type": "custom", 1076 "type": "custom",
1034 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\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 firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(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}",
1035 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1078 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1036 }, 1079 },
1037 { 1080 {
1038 - "name": "Copy checksum", 1081 + "name": "Copy checksum/URL",
1039 "icon": "content_copy", 1082 "icon": "content_copy",
1040 "type": "custom", 1083 "type": "custom",
1041 - "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}",
1042 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1085 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1043 } 1086 }
1044 ] 1087 ]
@@ -1272,6 +1315,27 @@ @@ -1272,6 +1315,27 @@
1272 "funcBody": null, 1315 "funcBody": null,
1273 "usePostProcessing": null, 1316 "usePostProcessing": null,
1274 "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
1275 } 1339 }
1276 ] 1340 ]
1277 } 1341 }
@@ -1297,24 +1361,24 @@ @@ -1297,24 +1361,24 @@
1297 "name": "Edit firmware", 1361 "name": "Edit firmware",
1298 "icon": "edit", 1362 "icon": "edit",
1299 "type": "customPretty", 1363 "type": "customPretty",
1300 - "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 mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-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>",  
1301 - "customCss": "", 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>",
  1365 + "customCss": "form {\n min-width: 300px !important;\n}",
1302 "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}",
1303 "customResources": [], 1367 "customResources": [],
1304 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1368 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1305 }, 1369 },
1306 { 1370 {
1307 - "name": "Download firware", 1371 + "name": "Download firmware",
1308 "icon": "file_download", 1372 "icon": "file_download",
1309 "type": "custom", 1373 "type": "custom",
1310 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\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 firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(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}",
1311 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1375 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1312 }, 1376 },
1313 { 1377 {
1314 - "name": "Copy checksum", 1378 + "name": "Copy checksum/URL",
1315 "icon": "content_copy", 1379 "icon": "content_copy",
1316 "type": "custom", 1380 "type": "custom",
1317 - "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}",
1318 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1382 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1319 } 1383 }
1320 ] 1384 ]
@@ -1548,6 +1612,27 @@ @@ -1548,6 +1612,27 @@
1548 "funcBody": null, 1612 "funcBody": null,
1549 "usePostProcessing": null, 1613 "usePostProcessing": null,
1550 "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
1551 } 1636 }
1552 ] 1637 ]
1553 } 1638 }
@@ -1573,24 +1658,24 @@ @@ -1573,24 +1658,24 @@
1573 "name": "Edit firmware", 1658 "name": "Edit firmware",
1574 "icon": "edit", 1659 "icon": "edit",
1575 "type": "customPretty", 1660 "type": "customPretty",
1576 - "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 mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-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>",  
1577 - "customCss": "", 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>",
  1662 + "customCss": "form {\n min-width: 300px !important;\n}",
1578 "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}",
1579 "customResources": [], 1664 "customResources": [],
1580 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1665 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1581 }, 1666 },
1582 { 1667 {
1583 - "name": "Download firware", 1668 + "name": "Download firmware",
1584 "icon": "file_download", 1669 "icon": "file_download",
1585 "type": "custom", 1670 "type": "custom",
1586 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\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 firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(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}",
1587 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1672 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1588 }, 1673 },
1589 { 1674 {
1590 - "name": "Copy checksum", 1675 + "name": "Copy checksum/URL",
1591 "icon": "content_copy", 1676 "icon": "content_copy",
1592 "type": "custom", 1677 "type": "custom",
1593 - "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}",
1594 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1679 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1595 } 1680 }
1596 ] 1681 ]
@@ -1824,6 +1909,27 @@ @@ -1824,6 +1909,27 @@
1824 "funcBody": null, 1909 "funcBody": null,
1825 "usePostProcessing": null, 1910 "usePostProcessing": null,
1826 "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
1827 } 1933 }
1828 ] 1934 ]
1829 } 1935 }
@@ -1849,24 +1955,24 @@ @@ -1849,24 +1955,24 @@
1849 "name": "Edit firmware", 1955 "name": "Edit firmware",
1850 "icon": "edit", 1956 "icon": "edit",
1851 "type": "customPretty", 1957 "type": "customPretty",
1852 - "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 mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-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>",  
1853 - "customCss": "", 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>",
  1959 + "customCss": "form {\n min-width: 300px !important;\n}",
1854 "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}",
1855 "customResources": [], 1961 "customResources": [],
1856 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" 1962 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1857 }, 1963 },
1858 { 1964 {
1859 - "name": "Download firware", 1965 + "name": "Download firmware",
1860 "icon": "file_download", 1966 "icon": "file_download",
1861 "type": "custom", 1967 "type": "custom",
1862 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\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 firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(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}",
1863 "id": "12533058-42f6-e75f-620c-219c48d01ec0" 1969 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1864 }, 1970 },
1865 { 1971 {
1866 - "name": "Copy checksum", 1972 + "name": "Copy checksum/URL",
1867 "icon": "content_copy", 1973 "icon": "content_copy",
1868 "type": "custom", 1974 "type": "custom",
1869 - "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}",
1870 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" 1976 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1871 } 1977 }
1872 ] 1978 ]
@@ -1935,7 +2041,7 @@ @@ -1935,7 +2041,7 @@
1935 } 2041 }
1936 }, 2042 },
1937 "device_firmware_history": { 2043 "device_firmware_history": {
1938 - "name": "Device Firmware history", 2044 + "name": "Firmware history: ${entityName}",
1939 "root": false, 2045 "root": false,
1940 "layouts": { 2046 "layouts": {
1941 "main": { 2047 "main": {
@@ -2378,7 +2484,8 @@ @@ -2378,7 +2484,8 @@
2378 "titleColor": "rgba(0,0,0,0.870588)", 2484 "titleColor": "rgba(0,0,0,0.870588)",
2379 "showFilters": true, 2485 "showFilters": true,
2380 "showDashboardLogo": false, 2486 "showDashboardLogo": false,
2381 - "dashboardLogoUrl": null 2487 + "dashboardLogoUrl": null,
  2488 + "showUpdateDashboardImage": false
2382 } 2489 }
2383 }, 2490 },
2384 "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 +}
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 "alias": "html_card", 28 "alias": "html_card",
29 "name": "HTML Card", 29 "name": "HTML Card",
30 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=", 30 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=",
31 - "description": "Useful to inject custom HTML code. Designed to displays static information only.", 31 + "description": "Useful to inject custom HTML code. Designed to display static information only.",
32 "descriptor": { 32 "descriptor": {
33 "type": "static", 33 "type": "static",
34 "sizeX": 7.5, 34 "sizeX": 7.5,
@@ -10,16 +10,16 @@ @@ -10,16 +10,16 @@
10 "alias": "rpc_debug_terminal", 10 "alias": "rpc_debug_terminal",
11 "name": "RPC debug terminal", 11 "name": "RPC debug terminal",
12 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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", 12 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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",
13 - "description": "Allows to send any RPC command using it's name and parameters to device. Useful for debug.", 13 + "description": "Allows to send any RPC command using its name and parameters to device. Useful for debug.",
14 "descriptor": { 14 "descriptor": {
15 "type": "rpc", 15 "type": "rpc",
16 "sizeX": 9.5, 16 "sizeX": 9.5,
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 }
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 "alias": "rpc_remote_shell", 28 "alias": "rpc_remote_shell",
29 "name": "RPC remote shell", 29 "name": "RPC remote shell",
30 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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", 30 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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",
31 - "description": "Allows to send emulate remote shell. Requires custom implementation on the target device to work properly.", 31 + "description": "Allows to emulate remote shell. Requires custom implementation on the target device to work properly.",
32 "descriptor": { 32 "descriptor": {
33 "type": "rpc", 33 "type": "rpc",
34 "sizeX": 9.5, 34 "sizeX": 9.5,
@@ -100,7 +100,7 @@ @@ -100,7 +100,7 @@
100 "alias": "lcd_bar_gauge", 100 "alias": "lcd_bar_gauge",
101 "name": "LCD bar gauge", 101 "name": "LCD bar gauge",
102 "image": "data:image/png;base64,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", 102 "image": "data:image/png;base64,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",
103 - "description": "Preconfigured gauge to display any value reading as an bar. Allows to configure value range, gradient colors and other settings.", 103 + "description": "Preconfigured gauge to display any value reading as a bar. Allows to configure value range, gradient colors and other settings.",
104 "descriptor": { 104 "descriptor": {
105 "type": "latest", 105 "type": "latest",
106 "sizeX": 2, 106 "sizeX": 2,
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 "alias": "device_admin_table", 10 "alias": "device_admin_table",
11 "name": "Device admin table", 11 "name": "Device admin table",
12 "image": "data:image/png;base64,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", 12 "image": "data:image/png;base64,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",
13 - "description": "Customized entity table widget with preconfigured actions to create update and delete devices.", 13 + "description": "Customized entity table widget with preconfigured actions to create, update and delete devices.",
14 "descriptor": { 14 "descriptor": {
15 "type": "latest", 15 "type": "latest",
16 "sizeX": 7.5, 16 "sizeX": 7.5,
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 "alias": "asset_admin_table", 28 "alias": "asset_admin_table",
29 "name": "Asset admin table", 29 "name": "Asset admin table",
30 "image": "data:image/png;base64,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", 30 "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAm6SURBVHja7d3pV1PXGsBh/7LobW+txes9YYhiSsAoRKKtA63YWsHSWhwQcQATB4oo9QKptnWgqFhFMbEFLBVEoggyRMIUEwImJif53Q8BynVd5YDpUuneH5KwszmbZ+Xd+7xrveSceQQHuh+95a17IMC8YI9H5i1vsqcnOG/AwxxonoF53fJcgMjd8x4xJ9ojAREQAREQARGQNwDS2Tmj4f13J+Zv/8sgNftCszjON7nPdbRk+18yvHzV+Iv87JnP5XAogYRSpdsvPUz2aUWQ9oLA/3ac3BktiMmkBNKUkJM3/jL43HMku1x7fHJsMDyOD01AApMP4y08mZIWZUY6ghOQ4BSIHH1I3s6GBC8QKl6uXlMPoZLlaqMNaFonrbTQoFVrtJGgaf1E0uwcgdBhjSZvSy4B7amM2OXVDQYp5RrUaYMc2XFME/fVGIBXmxCrbeLJ7gT1xjYo15fo1FuHI5BwZVLshuYoQ7yaW6GUc8BpXcvg0YR+zmrvDJXE9dIeV9lfG3/L71xd5AwDDC89Otxu2AdnNDW959S5BCTj3Z6C2PSmnn0aL9ekIAfVR3vrNRUAIWfeRqefzzO7BnelhCiXCrub0r6KQP6jq+8/pB2NLuRCUpDijUD+p2Gene7lwIYw8ukedm8FCnL+DC1XnR/KDKAvBnJyCUg10C9Vg0O6G4Gsm7J6ijKBm33QJnVTrguBTd1PfjbPEs+DrL2oaJ2bTCZTVpbJZDI5Xg7JMMND6SG0JRnLOoEHuvSTD4G09UVFRRnGKWvEU7E7e7WeMelm5M8NSNfAK9nAJTVGIJunLOeiTCB4Zd+2TKk9stiHpSbys3ko5RYVFemORxXSKekMBoP6COC5lK3O8oO3Jlez5Sn6z0pLS0sr/oS4V223Nh3Q45HqlUN2Gi81VU9Ankj15Gdjlw6WlpaW2qIaWkdW19XV1RV9+AxbL/Sqr/BrDzjjqvliYi+bgFzWhKBCD8vPANsVQZ5ITdAhtVOuB+5I3eRn45Xqor79BnTlAMPqG2ze4qNTfYutm5/SE1tHtdpG6NQ5+CQ/MrZW3UpHmg6O6HtpS5wWUpwawqsuC3tyJTvlUnV4bMuGMPnZkGPsx5P7MJqQOrUzctLbRq9xWVrs/hCOj5amxe4NES6JTUkydMIZSecBkHOk5Um71WN4MyTd6sxpIS1x2t/4IXZpfJF0g3JD5oexOnvk3Sefq1M1uf5oQkb6xzfhfgg9bB4ECHfeifSOtraHADraxnOYrja/3xmE0P0HAc8Twk4fhJz+yIPPGcYzDLhdE/lVyxgMt7hwjuIdDrU3+ybfHRifIpopikjjBURABERABERABERABERABERABERABERABERABERA5hgk7HgKPscsjzXscDgcDt+bAJFVmWBVzfK/HLMWL5y/eLHtzYCormNVyQTsXeB2DdplX6sbcNwLKDnazx/AsIuwwzcw9sABPI2UJl4DZH38mFUlOxLT1PspjE9e/HGydtFjzEt0yaMKIce13Jvv1C9NfqeSTvXKD6pfD6Q2dZ9VJd89zy//DBemhxtU98PxPz2a3xVOsyiEOOZ3Fq9Bf5ifFoU27+JK3OuB1NnfKVbJ7q0rtCq5cBP3VW5WVFyZn5i4sEAhhPQTq86gr6BTNZSwJDFe9fT1QCh8VyWb14Yap0BuL+gbcfmUQr5PXOBCX0H9/GcG08iIK/yaIE81Krly8bEVKvckRDYYS1fUK4W4FmSAPqFEt41LCw/v+vR1hFaorBfuloXlc0fulA03XsVV5ufCPXxnDl1XcrSOSoDkC6A3fVvhhztHv3e9rWd2V8kiL+gr3voU5eGmeuCQVeRaAiIgAiIgAiIgAiIgAiIgAjItpGtONBFaAiIgAiIgAiIgAiIgf1tI04n/8x2yroKCgoKutwrSlG7Ie05iN0w2+0wgoQCAPPPKuwwXALgYZLZXJxoc2mIwnHiu7/RkG3wx5McckNP+OLUsNWX7GMCJlNStQU6sSN2h4LIDI0tSV6ZPXAPh0EXeA0A9Wps/O0ejsXZoS3rTc722gvFme0loBZbZqf6UkmOEdh8FHuhlMn/pSpbZ0Dj9xJ5/gTX9z5/fAzyoIwX60SAQmEmFtzHdsLp26HkHZyci6+zL1sjlTfLydkqOwc9fAa4OyKkFyFAIaTSwt4qB5ey9wHuMGtdsWDh6aRe6L9fF27mc8NH6PTNwGAxrhpgdJJz2zQ4o+bqh5sPxT+5eehDMxh0Kisyef2xct6RtKuTkATzvj17ahe5XKg/JUh+lexTHlcFgSP+NWUJofHcASozm1O8oS10LzpQeYKAx5YECyOIR9xV9eApkmxXUo5d2oeujOu+xDn7e84oOpRCXBJQcw64NArhT70BfA5grlYUW74/uOz8J2VkzFeKOVwx5sWOmEHIsgN943G7v6ltm717VqgCyyN5avBrLth7zOMRq6KxaMAlhraVj455XdCiFjB0CrDfAcRhw5uXl5ZXRvP1LJdXmp3l5e06N4DNlXTjG5WYKoCrLctTfWs23blouMrxn+4F9Ste58QXbi0LIX9rO9PkyLykYt+fFn8ebAfn9izVlYWUQ4wu3+5oJSM2bn2tZCgpefNrynYic2E/6RBovIAIiIAIiIAIiIAIiIDOGiDq7CC0BERABERABERABEZC3GiLq7DfdIF9n6OrVOieEb/1oB5CvKazGtp+96o+iY2jWdXZic8Abg01bVqytZtfX55OvAeWqx4omtqRaDiV7o+ZoXDPrOjuxmht4Y7BtgpaVwVSZmm/AYUx8zLOqyq5pJh6RPGCqocp+EWdllQ/H71Dj62ir/sE9G4fxFerssdYE7zjEZgQoshDOuJf0mOyD1ZppLgbR/HHkOSazujfxXGmaXLMTEocs8T+a9PJsHK9QZ48dPLrTG4NNnf2ZtgW4vUbm3EGSHmOsCnRM8/3h65/jNJttxIxReAqyro9DTGBsnpVj9nX22MFA8tUYbEb7D+uBjhQnQ1LV1fgzo/37k/c+e/ncf6Qz2rDbTEyAnFowVY5DzJBzPVoOxRBa4mKwbSKcbqVf3wm9ZWVlS0zDFuQtV18++bN/d8J3ZmICHP8WMqzXc5DjhiwHIeV+tBzKIRRG1sjvybI+w2w2y0DSY3JzDmt7p5neurRwv+Y3YgJ49IVfb5JdcabNC4cs8aatmVFzKIT8EQD/bdz3gaaxxoaGhoYQ0OwnbL85/dbjsd4agdsh8Dc0hWDI2tcSsJhbG+WZO968OrvFPNPfeEPr7B13ZwERdXaRxguIgAiIgAjI3xwyZ24QPDdu2ewemBeYGzfRlufNjduay/wXtgu0d9kLWo8AAAAASUVORK5CYII=",
31 - "description": "Customized entity table widget with preconfigured actions to create update and delete assets.", 31 + "description": "Customized entity table widget with preconfigured actions to create, update and delete assets.",
32 "descriptor": { 32 "descriptor": {
33 "type": "latest", 33 "type": "latest",
34 "sizeX": 7.5, 34 "sizeX": 7.5,
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 "alias": "gateway_configuration", 10 "alias": "gateway_configuration",
11 "name": "Gateway Configuration", 11 "name": "Gateway Configuration",
12 "image": "data:image/png;base64,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", 12 "image": "data:image/png;base64,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",
13 - "description": "Allows to define widget gateway configuration for a single selected device.", 13 + "description": "Allows to define gateway configuration for a single selected device.",
14 "descriptor": { 14 "descriptor": {
15 "type": "static", 15 "type": "static",
16 "sizeX": 8, 16 "sizeX": 8,
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 "alias": "update_multiple_attributes", 28 "alias": "update_multiple_attributes",
29 "name": "Update Multiple Attributes", 29 "name": "Update Multiple Attributes",
30 "image": "data:image/png;base64,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", 30 "image": "data:image/png;base64,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",
31 - "description": "Allows to create an input form and set values for multiple attributes simultaniously.", 31 + "description": "Allows to create an input form and set values for multiple attributes simultaneously.",
32 "descriptor": { 32 "descriptor": {
33 "type": "latest", 33 "type": "latest",
34 "sizeX": 7.5, 34 "sizeX": 7.5,
@@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
46 "alias": "route_map", 46 "alias": "route_map",
47 "name": "Route Map", 47 "name": "Route Map",
48 "image": "data:image/png;base64,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", 48 "image": "data:image/png;base64,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",
49 - "description": "TTrip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", 49 + "description": "Trip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.",
50 "descriptor": { 50 "descriptor": {
51 "type": "timeseries", 51 "type": "timeseries",
52 "sizeX": 8.5, 52 "sizeX": 8.5,
@@ -59,14 +59,15 @@ CREATE TABLE IF NOT EXISTS resource ( @@ -59,14 +59,15 @@ CREATE TABLE IF NOT EXISTS resource (
59 CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) 59 CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
60 ); 60 );
61 61
62 -CREATE TABLE IF NOT EXISTS firmware (  
63 - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, 62 +CREATE TABLE IF NOT EXISTS ota_package (
  63 + id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY,
64 created_time bigint NOT NULL, 64 created_time bigint NOT NULL,
65 tenant_id uuid NOT NULL, 65 tenant_id uuid NOT NULL,
66 device_profile_id uuid, 66 device_profile_id uuid,
67 type varchar(32) NOT NULL, 67 type varchar(32) NOT NULL,
68 title varchar(255) NOT NULL, 68 title varchar(255) NOT NULL,
69 version varchar(255) NOT NULL, 69 version varchar(255) NOT NULL,
  70 + url varchar(255),
70 file_name varchar(255), 71 file_name varchar(255),
71 content_type varchar(255), 72 content_type varchar(255),
72 checksum_algorithm varchar(32), 73 checksum_algorithm varchar(32),
@@ -75,7 +76,68 @@ CREATE TABLE IF NOT EXISTS firmware ( @@ -75,7 +76,68 @@ CREATE TABLE IF NOT EXISTS firmware (
75 data_size bigint, 76 data_size bigint,
76 additional_info varchar, 77 additional_info varchar,
77 search_text varchar(255), 78 search_text varchar(255),
78 - CONSTRAINT firmware_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 +);
  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 + user_info_uri varchar(255),
  100 + user_name_attribute_name varchar(255),
  101 + jwk_set_uri varchar(255),
  102 + client_authentication_method varchar(255),
  103 + login_button_label varchar(255),
  104 + login_button_icon varchar(255),
  105 + allow_user_creation boolean,
  106 + activate_user boolean,
  107 + type varchar(31),
  108 + basic_email_attribute_key varchar(31),
  109 + basic_first_name_attribute_key varchar(31),
  110 + basic_last_name_attribute_key varchar(31),
  111 + basic_tenant_name_strategy varchar(31),
  112 + basic_tenant_name_pattern varchar(255),
  113 + basic_customer_name_pattern varchar(255),
  114 + basic_default_dashboard_name varchar(255),
  115 + basic_always_full_screen boolean,
  116 + custom_url varchar(255),
  117 + custom_username varchar(255),
  118 + custom_password varchar(255),
  119 + custom_send_token boolean,
  120 + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE
  121 +);
  122 +
  123 +CREATE TABLE IF NOT EXISTS oauth2_domain (
  124 + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY,
  125 + oauth2_params_id uuid NOT NULL,
  126 + created_time bigint NOT NULL,
  127 + domain_name varchar(255),
  128 + domain_scheme varchar(31),
  129 + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  130 + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme)
  131 +);
  132 +
  133 +CREATE TABLE IF NOT EXISTS oauth2_mobile (
  134 + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY,
  135 + oauth2_params_id uuid NOT NULL,
  136 + created_time bigint NOT NULL,
  137 + pkg_name varchar(255),
  138 + callback_url_scheme varchar(255),
  139 + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  140 + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name)
79 ); 141 );
80 142
81 ALTER TABLE dashboard 143 ALTER TABLE dashboard
@@ -101,13 +163,13 @@ DO $$ @@ -101,13 +163,13 @@ DO $$
101 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN 163 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
102 ALTER TABLE device_profile 164 ALTER TABLE device_profile
103 ADD CONSTRAINT fk_firmware_device_profile 165 ADD CONSTRAINT fk_firmware_device_profile
104 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 166 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
105 END IF; 167 END IF;
106 168
107 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN 169 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN
108 ALTER TABLE device_profile 170 ALTER TABLE device_profile
109 ADD CONSTRAINT fk_software_device_profile 171 ADD CONSTRAINT fk_software_device_profile
110 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 172 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
111 END IF; 173 END IF;
112 174
113 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN 175 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN
@@ -119,13 +181,13 @@ DO $$ @@ -119,13 +181,13 @@ DO $$
119 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN 181 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
120 ALTER TABLE device 182 ALTER TABLE device
121 ADD CONSTRAINT fk_firmware_device 183 ADD CONSTRAINT fk_firmware_device
122 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 184 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
123 END IF; 185 END IF;
124 186
125 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN 187 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN
126 ALTER TABLE device 188 ALTER TABLE device
127 ADD CONSTRAINT fk_software_device 189 ADD CONSTRAINT fk_software_device
128 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 190 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
129 END IF; 191 END IF;
130 END; 192 END;
131 $$; 193 $$;
@@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService; @@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
60 import org.thingsboard.server.dao.entityview.EntityViewService; 60 import org.thingsboard.server.dao.entityview.EntityViewService;
61 import org.thingsboard.server.dao.event.EventService; 61 import org.thingsboard.server.dao.event.EventService;
62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; 62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
  63 +import org.thingsboard.server.dao.ota.OtaPackageService;
63 import org.thingsboard.server.dao.relation.RelationService; 64 import org.thingsboard.server.dao.relation.RelationService;
  65 +import org.thingsboard.server.dao.resource.ResourceService;
64 import org.thingsboard.server.dao.rule.RuleChainService; 66 import org.thingsboard.server.dao.rule.RuleChainService;
65 import org.thingsboard.server.dao.rule.RuleNodeStateService; 67 import org.thingsboard.server.dao.rule.RuleNodeStateService;
66 import org.thingsboard.server.dao.tenant.TenantProfileService; 68 import org.thingsboard.server.dao.tenant.TenantProfileService;
@@ -311,6 +313,14 @@ public class ActorSystemContext { @@ -311,6 +313,14 @@ public class ActorSystemContext {
311 @Autowired(required = false) 313 @Autowired(required = false)
312 @Getter private EdgeRpcService edgeRpcService; 314 @Getter private EdgeRpcService edgeRpcService;
313 315
  316 + @Lazy
  317 + @Autowired(required = false)
  318 + @Getter private ResourceService resourceService;
  319 +
  320 + @Lazy
  321 + @Autowired(required = false)
  322 + @Getter private OtaPackageService otaPackageService;
  323 +
314 @Value("${actors.session.max_concurrent_sessions_per_device:1}") 324 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
315 @Getter 325 @Getter
316 private long maxConcurrentSessionsPerDevice; 326 private long maxConcurrentSessionsPerDevice;
@@ -192,6 +192,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -192,6 +192,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
192 syncSessionSet.add(key); 192 syncSessionSet.add(key);
193 } 193 }
194 }); 194 });
  195 + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions);
195 syncSessionSet.forEach(rpcSubscriptions::remove); 196 syncSessionSet.forEach(rpcSubscriptions::remove);
196 } 197 }
197 198
@@ -454,7 +455,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -454,7 +455,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
454 } else { 455 } else {
455 SessionInfoMetaData sessionMD = sessions.get(sessionId); 456 SessionInfoMetaData sessionMD = sessions.get(sessionId);
456 if (sessionMD == null) { 457 if (sessionMD == null) {
457 - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); 458 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
458 } 459 }
459 sessionMD.setSubscribedToAttributes(true); 460 sessionMD.setSubscribedToAttributes(true);
460 log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); 461 log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
@@ -475,7 +476,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -475,7 +476,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
475 } else { 476 } else {
476 SessionInfoMetaData sessionMD = sessions.get(sessionId); 477 SessionInfoMetaData sessionMD = sessions.get(sessionId);
477 if (sessionMD == null) { 478 if (sessionMD == null) {
478 - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); 479 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
479 } 480 }
480 sessionMD.setSubscribedToRPC(true); 481 sessionMD.setSubscribedToRPC(true);
481 log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); 482 log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
@@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService; @@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
69 import org.thingsboard.server.dao.entityview.EntityViewService; 69 import org.thingsboard.server.dao.entityview.EntityViewService;
70 import org.thingsboard.server.dao.nosql.CassandraStatementTask; 70 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
71 import org.thingsboard.server.dao.nosql.TbResultSetFuture; 71 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
  72 +import org.thingsboard.server.dao.ota.OtaPackageService;
72 import org.thingsboard.server.dao.relation.RelationService; 73 import org.thingsboard.server.dao.relation.RelationService;
  74 +import org.thingsboard.server.dao.resource.ResourceService;
73 import org.thingsboard.server.dao.rule.RuleChainService; 75 import org.thingsboard.server.dao.rule.RuleChainService;
74 import org.thingsboard.server.dao.tenant.TenantService; 76 import org.thingsboard.server.dao.tenant.TenantService;
75 import org.thingsboard.server.dao.timeseries.TimeseriesService; 77 import org.thingsboard.server.dao.timeseries.TimeseriesService;
@@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext { @@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext {
487 } 489 }
488 490
489 @Override 491 @Override
  492 + public ResourceService getResourceService() {
  493 + return mainCtx.getResourceService();
  494 + }
  495 +
  496 + @Override
  497 + public OtaPackageService getOtaPackageService() {
  498 + return mainCtx.getOtaPackageService();
  499 + }
  500 +
  501 + @Override
490 public RuleEngineDeviceProfileCache getDeviceProfileCache() { 502 public RuleEngineDeviceProfileCache getDeviceProfileCache() {
491 return mainCtx.getDeviceProfileCache(); 503 return mainCtx.getDeviceProfileCache();
492 } 504 }
@@ -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())) {
@@ -110,8 +110,11 @@ public class AlarmController extends BaseController { @@ -110,8 +110,11 @@ public class AlarmController extends BaseController {
110 checkParameter(ALARM_ID, strAlarmId); 110 checkParameter(ALARM_ID, strAlarmId);
111 try { 111 try {
112 AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); 112 AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
113 - checkAlarmId(alarmId, Operation.WRITE); 113 + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
114 114
  115 + logEntityAction(alarm.getOriginator(), alarm,
  116 + getCurrentUser().getCustomerId(),
  117 + ActionType.ALARM_DELETE, null);
115 sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED); 118 sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED);
116 119
117 return alarmService.deleteAlarm(getTenantId(), alarmId); 120 return alarmService.deleteAlarm(getTenantId(), alarmId);
@@ -17,7 +17,6 @@ package org.thingsboard.server.controller; @@ -17,7 +17,6 @@ package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 -import com.fasterxml.jackson.databind.node.ArrayNode;  
21 import com.fasterxml.jackson.databind.node.ObjectNode; 20 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import lombok.Getter; 21 import lombok.Getter;
23 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
@@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; @@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
31 import org.thingsboard.server.common.data.Customer; 30 import org.thingsboard.server.common.data.Customer;
32 import org.thingsboard.server.common.data.Dashboard; 31 import org.thingsboard.server.common.data.Dashboard;
33 import org.thingsboard.server.common.data.DashboardInfo; 32 import org.thingsboard.server.common.data.DashboardInfo;
34 -import org.thingsboard.server.common.data.DataConstants;  
35 import org.thingsboard.server.common.data.Device; 33 import org.thingsboard.server.common.data.Device;
36 import org.thingsboard.server.common.data.DeviceInfo; 34 import org.thingsboard.server.common.data.DeviceInfo;
37 import org.thingsboard.server.common.data.DeviceProfile; 35 import org.thingsboard.server.common.data.DeviceProfile;
@@ -39,8 +37,8 @@ import org.thingsboard.server.common.data.EdgeUtils; @@ -39,8 +37,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
39 import org.thingsboard.server.common.data.EntityType; 37 import org.thingsboard.server.common.data.EntityType;
40 import org.thingsboard.server.common.data.EntityView; 38 import org.thingsboard.server.common.data.EntityView;
41 import org.thingsboard.server.common.data.EntityViewInfo; 39 import org.thingsboard.server.common.data.EntityViewInfo;
42 -import org.thingsboard.server.common.data.Firmware;  
43 -import org.thingsboard.server.common.data.FirmwareInfo; 40 +import org.thingsboard.server.common.data.OtaPackage;
  41 +import org.thingsboard.server.common.data.OtaPackageInfo;
44 import org.thingsboard.server.common.data.HasName; 42 import org.thingsboard.server.common.data.HasName;
45 import org.thingsboard.server.common.data.HasTenantId; 43 import org.thingsboard.server.common.data.HasTenantId;
46 import org.thingsboard.server.common.data.TbResourceInfo; 44 import org.thingsboard.server.common.data.TbResourceInfo;
@@ -70,7 +68,7 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -70,7 +68,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
70 import org.thingsboard.server.common.data.id.EntityId; 68 import org.thingsboard.server.common.data.id.EntityId;
71 import org.thingsboard.server.common.data.id.EntityIdFactory; 69 import org.thingsboard.server.common.data.id.EntityIdFactory;
72 import org.thingsboard.server.common.data.id.EntityViewId; 70 import org.thingsboard.server.common.data.id.EntityViewId;
73 -import org.thingsboard.server.common.data.id.FirmwareId; 71 +import org.thingsboard.server.common.data.id.OtaPackageId;
74 import org.thingsboard.server.common.data.id.TbResourceId; 72 import org.thingsboard.server.common.data.id.TbResourceId;
75 import org.thingsboard.server.common.data.id.RuleChainId; 73 import org.thingsboard.server.common.data.id.RuleChainId;
76 import org.thingsboard.server.common.data.id.RuleNodeId; 74 import org.thingsboard.server.common.data.id.RuleNodeId;
@@ -79,10 +77,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; @@ -79,10 +77,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
79 import org.thingsboard.server.common.data.id.UserId; 77 import org.thingsboard.server.common.data.id.UserId;
80 import org.thingsboard.server.common.data.id.WidgetTypeId; 78 import org.thingsboard.server.common.data.id.WidgetTypeId;
81 import org.thingsboard.server.common.data.id.WidgetsBundleId; 79 import org.thingsboard.server.common.data.id.WidgetsBundleId;
82 -import org.thingsboard.server.common.data.kv.AttributeKvEntry;  
83 -import org.thingsboard.server.common.data.kv.DataType;  
84 -import org.thingsboard.server.common.data.kv.KvEntry;  
85 -import org.thingsboard.server.common.data.kv.TsKvEntry;  
86 import org.thingsboard.server.common.data.page.PageLink; 80 import org.thingsboard.server.common.data.page.PageLink;
87 import org.thingsboard.server.common.data.page.SortOrder; 81 import org.thingsboard.server.common.data.page.SortOrder;
88 import org.thingsboard.server.common.data.page.TimePageLink; 82 import org.thingsboard.server.common.data.page.TimePageLink;
@@ -94,9 +88,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; @@ -94,9 +88,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
94 import org.thingsboard.server.common.data.rule.RuleNode; 88 import org.thingsboard.server.common.data.rule.RuleNode;
95 import org.thingsboard.server.common.data.widget.WidgetTypeDetails; 89 import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
96 import org.thingsboard.server.common.data.widget.WidgetsBundle; 90 import org.thingsboard.server.common.data.widget.WidgetsBundle;
97 -import org.thingsboard.server.common.msg.TbMsg;  
98 -import org.thingsboard.server.common.msg.TbMsgDataType;  
99 -import org.thingsboard.server.common.msg.TbMsgMetaData;  
100 import org.thingsboard.server.dao.asset.AssetService; 91 import org.thingsboard.server.dao.asset.AssetService;
101 import org.thingsboard.server.dao.attributes.AttributesService; 92 import org.thingsboard.server.dao.attributes.AttributesService;
102 import org.thingsboard.server.dao.audit.AuditLogService; 93 import org.thingsboard.server.dao.audit.AuditLogService;
@@ -110,7 +101,7 @@ import org.thingsboard.server.dao.edge.EdgeService; @@ -110,7 +101,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
110 import org.thingsboard.server.dao.entityview.EntityViewService; 101 import org.thingsboard.server.dao.entityview.EntityViewService;
111 import org.thingsboard.server.dao.exception.DataValidationException; 102 import org.thingsboard.server.dao.exception.DataValidationException;
112 import org.thingsboard.server.dao.exception.IncorrectParameterException; 103 import org.thingsboard.server.dao.exception.IncorrectParameterException;
113 -import org.thingsboard.server.dao.firmware.FirmwareService; 104 +import org.thingsboard.server.dao.ota.OtaPackageService;
114 import org.thingsboard.server.dao.model.ModelConstants; 105 import org.thingsboard.server.dao.model.ModelConstants;
115 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; 106 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
116 import org.thingsboard.server.dao.oauth2.OAuth2Service; 107 import org.thingsboard.server.dao.oauth2.OAuth2Service;
@@ -127,8 +118,9 @@ import org.thingsboard.server.gen.transport.TransportProtos; @@ -127,8 +118,9 @@ import org.thingsboard.server.gen.transport.TransportProtos;
127 import org.thingsboard.server.queue.discovery.PartitionService; 118 import org.thingsboard.server.queue.discovery.PartitionService;
128 import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 119 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
129 import org.thingsboard.server.queue.util.TbCoreComponent; 120 import org.thingsboard.server.queue.util.TbCoreComponent;
  121 +import org.thingsboard.server.service.action.RuleEngineEntityActionService;
130 import org.thingsboard.server.service.component.ComponentDiscoveryService; 122 import org.thingsboard.server.service.component.ComponentDiscoveryService;
131 -import org.thingsboard.server.service.firmware.FirmwareStateService; 123 +import org.thingsboard.server.service.ota.OtaPackageStateService;
132 import org.thingsboard.server.service.edge.EdgeNotificationService; 124 import org.thingsboard.server.service.edge.EdgeNotificationService;
133 import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; 125 import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
134 import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; 126 import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
@@ -147,11 +139,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -147,11 +139,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
147 import javax.mail.MessagingException; 139 import javax.mail.MessagingException;
148 import javax.servlet.http.HttpServletResponse; 140 import javax.servlet.http.HttpServletResponse;
149 import java.util.List; 141 import java.util.List;
150 -import java.util.Map;  
151 import java.util.Optional; 142 import java.util.Optional;
152 import java.util.Set; 143 import java.util.Set;
153 import java.util.UUID; 144 import java.util.UUID;
154 -import java.util.stream.Collectors;  
155 145
156 import static org.thingsboard.server.dao.service.Validator.validateId; 146 import static org.thingsboard.server.dao.service.Validator.validateId;
157 147
@@ -250,10 +240,10 @@ public abstract class BaseController { @@ -250,10 +240,10 @@ public abstract class BaseController {
250 protected TbResourceService resourceService; 240 protected TbResourceService resourceService;
251 241
252 @Autowired 242 @Autowired
253 - protected FirmwareService firmwareService; 243 + protected OtaPackageService otaPackageService;
254 244
255 @Autowired 245 @Autowired
256 - protected FirmwareStateService firmwareStateService; 246 + protected OtaPackageStateService otaPackageStateService;
257 247
258 @Autowired 248 @Autowired
259 protected TbQueueProducerProvider producerProvider; 249 protected TbQueueProducerProvider producerProvider;
@@ -279,6 +269,9 @@ public abstract class BaseController { @@ -279,6 +269,9 @@ public abstract class BaseController {
279 @Autowired(required = false) 269 @Autowired(required = false)
280 protected EdgeGrpcService edgeGrpcService; 270 protected EdgeGrpcService edgeGrpcService;
281 271
  272 + @Autowired
  273 + protected RuleEngineEntityActionService ruleEngineEntityActionService;
  274 +
282 @Value("${server.log_controller_error_stack_trace}") 275 @Value("${server.log_controller_error_stack_trace}")
283 @Getter 276 @Getter
284 private boolean logControllerErrorStackTrace; 277 private boolean logControllerErrorStackTrace;
@@ -511,8 +504,8 @@ public abstract class BaseController { @@ -511,8 +504,8 @@ public abstract class BaseController {
511 case TB_RESOURCE: 504 case TB_RESOURCE:
512 checkResourceId(new TbResourceId(entityId.getId()), operation); 505 checkResourceId(new TbResourceId(entityId.getId()), operation);
513 return; 506 return;
514 - case FIRMWARE:  
515 - checkFirmwareId(new FirmwareId(entityId.getId()), operation); 507 + case OTA_PACKAGE:
  508 + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);
516 return; 509 return;
517 default: 510 default:
518 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); 511 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
@@ -769,25 +762,25 @@ public abstract class BaseController { @@ -769,25 +762,25 @@ public abstract class BaseController {
769 } 762 }
770 } 763 }
771 764
772 - Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { 765 + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
773 try { 766 try {
774 - validateId(firmwareId, "Incorrect firmwareId " + firmwareId);  
775 - Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId);  
776 - checkNotNull(firmware);  
777 - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware);  
778 - return firmware; 767 + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
  768 + OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId);
  769 + checkNotNull(otaPackage);
  770 + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage);
  771 + return otaPackage;
779 } catch (Exception e) { 772 } catch (Exception e) {
780 throw handleException(e, false); 773 throw handleException(e, false);
781 } 774 }
782 } 775 }
783 776
784 - FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { 777 + OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
785 try { 778 try {
786 - validateId(firmwareId, "Incorrect firmwareId " + firmwareId);  
787 - FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId);  
788 - checkNotNull(firmwareInfo);  
789 - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo);  
790 - return firmwareInfo; 779 + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
  780 + OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId);
  781 + checkNotNull(otaPackageIn);
  782 + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn);
  783 + return otaPackageIn;
791 } catch (Exception e) { 784 } catch (Exception e) {
792 throw handleException(e, false); 785 throw handleException(e, false);
793 } 786 }
@@ -809,7 +802,7 @@ public abstract class BaseController { @@ -809,7 +802,7 @@ public abstract class BaseController {
809 customerId = user.getCustomerId(); 802 customerId = user.getCustomerId();
810 } 803 }
811 if (e == null) { 804 if (e == null) {
812 - pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo); 805 + ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo);
813 } 806 }
814 auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); 807 auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
815 } 808 }
@@ -819,184 +812,6 @@ public abstract class BaseController { @@ -819,184 +812,6 @@ public abstract class BaseController {
819 return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; 812 return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
820 } 813 }
821 814
822 - private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,  
823 - ActionType actionType, Object... additionalInfo) {  
824 - String msgType = null;  
825 - switch (actionType) {  
826 - case ADDED:  
827 - msgType = DataConstants.ENTITY_CREATED;  
828 - break;  
829 - case DELETED:  
830 - msgType = DataConstants.ENTITY_DELETED;  
831 - break;  
832 - case UPDATED:  
833 - msgType = DataConstants.ENTITY_UPDATED;  
834 - break;  
835 - case ASSIGNED_TO_CUSTOMER:  
836 - msgType = DataConstants.ENTITY_ASSIGNED;  
837 - break;  
838 - case UNASSIGNED_FROM_CUSTOMER:  
839 - msgType = DataConstants.ENTITY_UNASSIGNED;  
840 - break;  
841 - case ATTRIBUTES_UPDATED:  
842 - msgType = DataConstants.ATTRIBUTES_UPDATED;  
843 - break;  
844 - case ATTRIBUTES_DELETED:  
845 - msgType = DataConstants.ATTRIBUTES_DELETED;  
846 - break;  
847 - case ALARM_ACK:  
848 - msgType = DataConstants.ALARM_ACK;  
849 - break;  
850 - case ALARM_CLEAR:  
851 - msgType = DataConstants.ALARM_CLEAR;  
852 - break;  
853 - case ASSIGNED_FROM_TENANT:  
854 - msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;  
855 - break;  
856 - case ASSIGNED_TO_TENANT:  
857 - msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;  
858 - break;  
859 - case PROVISION_SUCCESS:  
860 - msgType = DataConstants.PROVISION_SUCCESS;  
861 - break;  
862 - case PROVISION_FAILURE:  
863 - msgType = DataConstants.PROVISION_FAILURE;  
864 - break;  
865 - case TIMESERIES_UPDATED:  
866 - msgType = DataConstants.TIMESERIES_UPDATED;  
867 - break;  
868 - case TIMESERIES_DELETED:  
869 - msgType = DataConstants.TIMESERIES_DELETED;  
870 - break;  
871 - case ASSIGNED_TO_EDGE:  
872 - msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;  
873 - break;  
874 - case UNASSIGNED_FROM_EDGE:  
875 - msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;  
876 - break;  
877 - }  
878 - if (!StringUtils.isEmpty(msgType)) {  
879 - try {  
880 - TbMsgMetaData metaData = new TbMsgMetaData();  
881 - metaData.putValue("userId", user.getId().toString());  
882 - metaData.putValue("userName", user.getName());  
883 - if (customerId != null && !customerId.isNullUid()) {  
884 - metaData.putValue("customerId", customerId.toString());  
885 - }  
886 - if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) {  
887 - String strCustomerId = extractParameter(String.class, 1, additionalInfo);  
888 - String strCustomerName = extractParameter(String.class, 2, additionalInfo);  
889 - metaData.putValue("assignedCustomerId", strCustomerId);  
890 - metaData.putValue("assignedCustomerName", strCustomerName);  
891 - } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) {  
892 - String strCustomerId = extractParameter(String.class, 1, additionalInfo);  
893 - String strCustomerName = extractParameter(String.class, 2, additionalInfo);  
894 - metaData.putValue("unassignedCustomerId", strCustomerId);  
895 - metaData.putValue("unassignedCustomerName", strCustomerName);  
896 - } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {  
897 - String strTenantId = extractParameter(String.class, 0, additionalInfo);  
898 - String strTenantName = extractParameter(String.class, 1, additionalInfo);  
899 - metaData.putValue("assignedFromTenantId", strTenantId);  
900 - metaData.putValue("assignedFromTenantName", strTenantName);  
901 - } else if (actionType == ActionType.ASSIGNED_TO_TENANT) {  
902 - String strTenantId = extractParameter(String.class, 0, additionalInfo);  
903 - String strTenantName = extractParameter(String.class, 1, additionalInfo);  
904 - metaData.putValue("assignedToTenantId", strTenantId);  
905 - metaData.putValue("assignedToTenantName", strTenantName);  
906 - } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {  
907 - String strEdgeId = extractParameter(String.class, 1, additionalInfo);  
908 - String strEdgeName = extractParameter(String.class, 2, additionalInfo);  
909 - metaData.putValue("assignedEdgeId", strEdgeId);  
910 - metaData.putValue("assignedEdgeName", strEdgeName);  
911 - } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {  
912 - String strEdgeId = extractParameter(String.class, 1, additionalInfo);  
913 - String strEdgeName = extractParameter(String.class, 2, additionalInfo);  
914 - metaData.putValue("unassignedEdgeId", strEdgeId);  
915 - metaData.putValue("unassignedEdgeName", strEdgeName);  
916 - }  
917 - ObjectNode entityNode;  
918 - if (entity != null) {  
919 - entityNode = json.valueToTree(entity);  
920 - if (entityId.getEntityType() == EntityType.DASHBOARD) {  
921 - entityNode.put("configuration", "");  
922 - }  
923 - } else {  
924 - entityNode = json.createObjectNode();  
925 - if (actionType == ActionType.ATTRIBUTES_UPDATED) {  
926 - String scope = extractParameter(String.class, 0, additionalInfo);  
927 - @SuppressWarnings("unchecked")  
928 - List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);  
929 - metaData.putValue(DataConstants.SCOPE, scope);  
930 - if (attributes != null) {  
931 - for (AttributeKvEntry attr : attributes) {  
932 - addKvEntry(entityNode, attr);  
933 - }  
934 - }  
935 - } else if (actionType == ActionType.ATTRIBUTES_DELETED) {  
936 - String scope = extractParameter(String.class, 0, additionalInfo);  
937 - @SuppressWarnings("unchecked")  
938 - List<String> keys = extractParameter(List.class, 1, additionalInfo);  
939 - metaData.putValue(DataConstants.SCOPE, scope);  
940 - ArrayNode attrsArrayNode = entityNode.putArray("attributes");  
941 - if (keys != null) {  
942 - keys.forEach(attrsArrayNode::add);  
943 - }  
944 - } else if (actionType == ActionType.TIMESERIES_UPDATED) {  
945 - @SuppressWarnings("unchecked")  
946 - List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo);  
947 - addTimeseries(entityNode, timeseries);  
948 - } else if (actionType == ActionType.TIMESERIES_DELETED) {  
949 - @SuppressWarnings("unchecked")  
950 - List<String> keys = extractParameter(List.class, 0, additionalInfo);  
951 - if (keys != null) {  
952 - ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries");  
953 - keys.forEach(timeseriesArrayNode::add);  
954 - }  
955 - entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo));  
956 - entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo));  
957 - }  
958 - }  
959 - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));  
960 - TenantId tenantId = user.getTenantId();  
961 - if (tenantId.isNullUid()) {  
962 - if (entity instanceof HasTenantId) {  
963 - tenantId = ((HasTenantId) entity).getTenantId();  
964 - }  
965 - }  
966 - tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);  
967 - } catch (Exception e) {  
968 - log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);  
969 - }  
970 - }  
971 - }  
972 -  
973 - private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception {  
974 - if (kvEntry.getDataType() == DataType.BOOLEAN) {  
975 - kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));  
976 - } else if (kvEntry.getDataType() == DataType.DOUBLE) {  
977 - kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));  
978 - } else if (kvEntry.getDataType() == DataType.LONG) {  
979 - kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));  
980 - } else if (kvEntry.getDataType() == DataType.JSON) {  
981 - if (kvEntry.getJsonValue().isPresent()) {  
982 - entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get()));  
983 - }  
984 - } else {  
985 - entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString());  
986 - }  
987 - }  
988 -  
989 - private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {  
990 - T result = null;  
991 - if (additionalInfo != null && additionalInfo.length > index) {  
992 - Object paramObject = additionalInfo[index];  
993 - if (clazz.isInstance(paramObject)) {  
994 - result = clazz.cast(paramObject);  
995 - }  
996 - }  
997 - return result;  
998 - }  
999 -  
1000 protected <E extends HasName> String entityToStr(E entity) { 815 protected <E extends HasName> String entityToStr(E entity) {
1001 try { 816 try {
1002 return json.writeValueAsString(json.valueToTree(entity)); 817 return json.writeValueAsString(json.valueToTree(entity));
@@ -1093,23 +908,6 @@ public abstract class BaseController { @@ -1093,23 +908,6 @@ public abstract class BaseController {
1093 return result; 908 return result;
1094 } 909 }
1095 910
1096 - private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception {  
1097 - if (timeseries != null && !timeseries.isEmpty()) {  
1098 - ArrayNode result = entityNode.putArray("timeseries");  
1099 - Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream()  
1100 - .collect(Collectors.groupingBy(TsKvEntry::getTs));  
1101 - for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) {  
1102 - ObjectNode element = json.createObjectNode();  
1103 - element.put("ts", entry.getKey());  
1104 - ObjectNode values = element.putObject("values");  
1105 - for (TsKvEntry tsKvEntry : entry.getValue()) {  
1106 - addKvEntry(values, tsKvEntry);  
1107 - }  
1108 - result.add(element);  
1109 - }  
1110 - }  
1111 - }  
1112 -  
1113 protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { 911 protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException {
1114 String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; 912 String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null;
1115 if (dashboardId != null && !dashboardId.equals("null")) { 913 if (dashboardId != null && !dashboardId.equals("null")) {
@@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
53 import org.thingsboard.server.common.data.id.DeviceProfileId; 53 import org.thingsboard.server.common.data.id.DeviceProfileId;
54 import org.thingsboard.server.common.data.id.EdgeId; 54 import org.thingsboard.server.common.data.id.EdgeId;
55 import org.thingsboard.server.common.data.id.TenantId; 55 import org.thingsboard.server.common.data.id.TenantId;
  56 +import org.thingsboard.server.common.data.ota.OtaPackageType;
56 import org.thingsboard.server.common.data.page.PageData; 57 import org.thingsboard.server.common.data.page.PageData;
57 import org.thingsboard.server.common.data.page.PageLink; 58 import org.thingsboard.server.common.data.page.PageLink;
58 import org.thingsboard.server.common.data.page.TimePageLink; 59 import org.thingsboard.server.common.data.page.TimePageLink;
@@ -75,6 +76,7 @@ import javax.annotation.Nullable; @@ -75,6 +76,7 @@ import javax.annotation.Nullable;
75 import java.io.IOException; 76 import java.io.IOException;
76 import java.util.ArrayList; 77 import java.util.ArrayList;
77 import java.util.List; 78 import java.util.List;
  79 +import java.util.UUID;
78 import java.util.stream.Collectors; 80 import java.util.stream.Collectors;
79 81
80 import static org.thingsboard.server.controller.EdgeController.EDGE_ID; 82 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@@ -153,7 +155,7 @@ public class DeviceController extends BaseController { @@ -153,7 +155,7 @@ public class DeviceController extends BaseController {
153 deviceStateService.onDeviceUpdated(savedDevice); 155 deviceStateService.onDeviceUpdated(savedDevice);
154 } 156 }
155 157
156 - firmwareStateService.update(savedDevice, oldDevice); 158 + otaPackageStateService.update(savedDevice, oldDevice);
157 159
158 return savedDevice; 160 return savedDevice;
159 } catch (Exception e) { 161 } catch (Exception e) {
@@ -778,4 +780,19 @@ public class DeviceController extends BaseController { @@ -778,4 +780,19 @@ public class DeviceController extends BaseController {
778 throw handleException(e); 780 throw handleException(e);
779 } 781 }
780 } 782 }
  783 +
  784 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  785 + @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET)
  786 + @ResponseBody
  787 + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
  788 + @RequestParam String deviceProfileId) throws ThingsboardException {
  789 + checkParameter("OtaPackageType", otaPackageType);
  790 + checkParameter("DeviceProfileId", deviceProfileId);
  791 + try {
  792 + return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(
  793 + getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType));
  794 + } catch (Exception e) {
  795 + throw handleException(e);
  796 + }
  797 + }
781 } 798 }
@@ -168,7 +168,7 @@ public class DeviceProfileController extends BaseController { @@ -168,7 +168,7 @@ public class DeviceProfileController extends BaseController {
168 null, 168 null,
169 created ? ActionType.ADDED : ActionType.UPDATED, null); 169 created ? ActionType.ADDED : ActionType.UPDATED, null);
170 170
171 - firmwareStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); 171 + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged);
172 172
173 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(), 173 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),
174 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); 174 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
@@ -22,12 +22,13 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -22,12 +22,13 @@ 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;
28 import org.thingsboard.server.common.data.exception.ThingsboardException; 29 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 30 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
30 -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; 31 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
31 import org.thingsboard.server.dao.oauth2.OAuth2Configuration; 32 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
32 import org.thingsboard.server.queue.util.TbCoreComponent; 33 import org.thingsboard.server.queue.util.TbCoreComponent;
33 import org.thingsboard.server.service.security.permission.Operation; 34 import org.thingsboard.server.service.security.permission.Operation;
@@ -49,7 +50,8 @@ public class OAuth2Controller extends BaseController { @@ -49,7 +50,8 @@ public class OAuth2Controller extends BaseController {
49 50
50 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) 51 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
51 @ResponseBody 52 @ResponseBody
52 - public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { 53 + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request,
  54 + @RequestParam(required = false) String pkgName) throws ThingsboardException {
53 try { 55 try {
54 if (log.isDebugEnabled()) { 56 if (log.isDebugEnabled()) {
55 log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); 57 log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
@@ -59,7 +61,7 @@ public class OAuth2Controller extends BaseController { @@ -59,7 +61,7 @@ public class OAuth2Controller extends BaseController {
59 log.debug("Header: {} {}", header, request.getHeader(header)); 61 log.debug("Header: {} {}", header, request.getHeader(header));
60 } 62 }
61 } 63 }
62 - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); 64 + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName);
63 } catch (Exception e) { 65 } catch (Exception e) {
64 throw handleException(e); 66 throw handleException(e);
65 } 67 }
@@ -68,10 +70,10 @@ public class OAuth2Controller extends BaseController { @@ -68,10 +70,10 @@ public class OAuth2Controller extends BaseController {
68 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") 70 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
69 @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") 71 @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json")
70 @ResponseBody 72 @ResponseBody
71 - public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { 73 + public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException {
72 try { 74 try {
73 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); 75 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
74 - return oAuth2Service.findOAuth2Params(); 76 + return oAuth2Service.findOAuth2Info();
75 } catch (Exception e) { 77 } catch (Exception e) {
76 throw handleException(e); 78 throw handleException(e);
77 } 79 }
@@ -80,11 +82,11 @@ public class OAuth2Controller extends BaseController { @@ -80,11 +82,11 @@ public class OAuth2Controller extends BaseController {
80 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") 82 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
81 @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) 83 @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
82 @ResponseStatus(value = HttpStatus.OK) 84 @ResponseStatus(value = HttpStatus.OK)
83 - public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { 85 + public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException {
84 try { 86 try {
85 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); 87 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE);
86 - oAuth2Service.saveOAuth2Params(oauth2Params);  
87 - return oAuth2Service.findOAuth2Params(); 88 + oAuth2Service.saveOAuth2Info(oauth2Info);
  89 + return oAuth2Service.findOAuth2Info();
88 } catch (Exception e) { 90 } catch (Exception e) {
89 throw handleException(e); 91 throw handleException(e);
90 } 92 }
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java renamed from application/src/main/java/org/thingsboard/server/controller/FirmwareController.java
@@ -30,14 +30,14 @@ import org.springframework.web.bind.annotation.ResponseBody; @@ -30,14 +30,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
30 import org.springframework.web.bind.annotation.RestController; 30 import org.springframework.web.bind.annotation.RestController;
31 import org.springframework.web.multipart.MultipartFile; 31 import org.springframework.web.multipart.MultipartFile;
32 import org.thingsboard.server.common.data.EntityType; 32 import org.thingsboard.server.common.data.EntityType;
33 -import org.thingsboard.server.common.data.Firmware;  
34 -import org.thingsboard.server.common.data.FirmwareInfo; 33 +import org.thingsboard.server.common.data.OtaPackage;
  34 +import org.thingsboard.server.common.data.OtaPackageInfo;
35 import org.thingsboard.server.common.data.audit.ActionType; 35 import org.thingsboard.server.common.data.audit.ActionType;
36 import org.thingsboard.server.common.data.exception.ThingsboardException; 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;  
38 -import org.thingsboard.server.common.data.firmware.FirmwareType; 37 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  38 +import org.thingsboard.server.common.data.ota.OtaPackageType;
39 import org.thingsboard.server.common.data.id.DeviceProfileId; 39 import org.thingsboard.server.common.data.id.DeviceProfileId;
40 -import org.thingsboard.server.common.data.id.FirmwareId; 40 +import org.thingsboard.server.common.data.id.OtaPackageId;
41 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
42 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
43 import org.thingsboard.server.queue.util.TbCoreComponent; 43 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -50,26 +50,30 @@ import java.nio.ByteBuffer; @@ -50,26 +50,30 @@ import java.nio.ByteBuffer;
50 @RestController 50 @RestController
51 @TbCoreComponent 51 @TbCoreComponent
52 @RequestMapping("/api") 52 @RequestMapping("/api")
53 -public class FirmwareController extends BaseController { 53 +public class OtaPackageController extends BaseController {
54 54
55 - public static final String FIRMWARE_ID = "firmwareId"; 55 + public static final String OTA_PACKAGE_ID = "otaPackageId";
56 public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm"; 56 public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm";
57 57
58 @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") 58 @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
59 - @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) 59 + @RequestMapping(value = "/otaPackage/{otaPackageId}/download", method = RequestMethod.GET)
60 @ResponseBody 60 @ResponseBody
61 - public ResponseEntity<org.springframework.core.io.Resource> downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
62 - checkParameter(FIRMWARE_ID, strFirmwareId); 61 + public ResponseEntity<org.springframework.core.io.Resource> downloadOtaPackage(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  62 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
63 try { 63 try {
64 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
65 - Firmware firmware = checkFirmwareId(firmwareId, Operation.READ); 64 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  65 + OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ);
66 66
67 - ByteArrayResource resource = new ByteArrayResource(firmware.getData().array()); 67 + if (otaPackage.hasUrl()) {
  68 + return ResponseEntity.badRequest().build();
  69 + }
  70 +
  71 + ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array());
68 return ResponseEntity.ok() 72 return ResponseEntity.ok()
69 - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName())  
70 - .header("x-filename", firmware.getFileName()) 73 + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName())
  74 + .header("x-filename", otaPackage.getFileName())
71 .contentLength(resource.contentLength()) 75 .contentLength(resource.contentLength())
72 - .contentType(parseMediaType(firmware.getContentType())) 76 + .contentType(parseMediaType(otaPackage.getContentType()))
73 .body(resource); 77 .body(resource);
74 } catch (Exception e) { 78 } catch (Exception e) {
75 throw handleException(e); 79 throw handleException(e);
@@ -77,144 +81,143 @@ public class FirmwareController extends BaseController { @@ -77,144 +81,143 @@ public class FirmwareController extends BaseController {
77 } 81 }
78 82
79 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 83 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
80 - @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) 84 + @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET)
81 @ResponseBody 85 @ResponseBody
82 - public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
83 - checkParameter(FIRMWARE_ID, strFirmwareId); 86 + public OtaPackageInfo getOtaPackageInfoById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  87 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
84 try { 88 try {
85 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
86 - return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId)); 89 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  90 + return checkNotNull(otaPackageService.findOtaPackageInfoById(getTenantId(), otaPackageId));
87 } catch (Exception e) { 91 } catch (Exception e) {
88 throw handleException(e); 92 throw handleException(e);
89 } 93 }
90 } 94 }
91 95
92 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 96 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
93 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET) 97 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
94 @ResponseBody 98 @ResponseBody
95 - public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
96 - checkParameter(FIRMWARE_ID, strFirmwareId); 99 + public OtaPackage getOtaPackageById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  100 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
97 try { 101 try {
98 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
99 - return checkFirmwareId(firmwareId, Operation.READ); 102 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  103 + return checkOtaPackageId(otaPackageId, Operation.READ);
100 } catch (Exception e) { 104 } catch (Exception e) {
101 throw handleException(e); 105 throw handleException(e);
102 } 106 }
103 } 107 }
104 108
105 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 109 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
106 - @RequestMapping(value = "/firmware", method = RequestMethod.POST) 110 + @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
107 @ResponseBody 111 @ResponseBody
108 - public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException {  
109 - boolean created = firmwareInfo.getId() == null; 112 + public OtaPackageInfo saveOtaPackageInfo(@RequestBody OtaPackageInfo otaPackageInfo) throws ThingsboardException {
  113 + boolean created = otaPackageInfo.getId() == null;
110 try { 114 try {
111 - firmwareInfo.setTenantId(getTenantId());  
112 - checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);  
113 - FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo);  
114 - logEntityAction(savedFirmwareInfo.getId(), savedFirmwareInfo, 115 + otaPackageInfo.setTenantId(getTenantId());
  116 + checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE);
  117 + OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(otaPackageInfo);
  118 + logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo,
115 null, created ? ActionType.ADDED : ActionType.UPDATED, null); 119 null, created ? ActionType.ADDED : ActionType.UPDATED, null);
116 - return savedFirmwareInfo; 120 + return savedOtaPackageInfo;
117 } catch (Exception e) { 121 } catch (Exception e) {
118 - logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo, 122 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), otaPackageInfo,
119 null, created ? ActionType.ADDED : ActionType.UPDATED, e); 123 null, created ? ActionType.ADDED : ActionType.UPDATED, e);
120 throw handleException(e); 124 throw handleException(e);
121 } 125 }
122 } 126 }
123 127
124 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 128 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
125 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) 129 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST)
126 @ResponseBody 130 @ResponseBody
127 - public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId,  
128 - @RequestParam(required = false) String checksum,  
129 - @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,  
130 - @RequestBody MultipartFile file) throws ThingsboardException {  
131 - checkParameter(FIRMWARE_ID, strFirmwareId); 131 + public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId,
  132 + @RequestParam(required = false) String checksum,
  133 + @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,
  134 + @RequestBody MultipartFile file) throws ThingsboardException {
  135 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
132 checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); 136 checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr);
133 try { 137 try {
134 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
135 - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ);  
136 -  
137 - Firmware firmware = new Firmware(firmwareId);  
138 - firmware.setCreatedTime(info.getCreatedTime());  
139 - firmware.setTenantId(getTenantId());  
140 - firmware.setDeviceProfileId(info.getDeviceProfileId());  
141 - firmware.setType(info.getType());  
142 - firmware.setTitle(info.getTitle());  
143 - firmware.setVersion(info.getVersion());  
144 - firmware.setAdditionalInfo(info.getAdditionalInfo()); 138 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  139 + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.READ);
  140 +
  141 + OtaPackage otaPackage = new OtaPackage(otaPackageId);
  142 + otaPackage.setCreatedTime(info.getCreatedTime());
  143 + otaPackage.setTenantId(getTenantId());
  144 + otaPackage.setDeviceProfileId(info.getDeviceProfileId());
  145 + otaPackage.setType(info.getType());
  146 + otaPackage.setTitle(info.getTitle());
  147 + otaPackage.setVersion(info.getVersion());
  148 + otaPackage.setAdditionalInfo(info.getAdditionalInfo());
145 149
146 ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); 150 ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());
147 151
148 byte[] bytes = file.getBytes(); 152 byte[] bytes = file.getBytes();
149 if (StringUtils.isEmpty(checksum)) { 153 if (StringUtils.isEmpty(checksum)) {
150 - checksum = firmwareService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes)); 154 + checksum = otaPackageService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes));
151 } 155 }
152 156
153 - firmware.setChecksumAlgorithm(checksumAlgorithm);  
154 - firmware.setChecksum(checksum);  
155 - firmware.setFileName(file.getOriginalFilename());  
156 - firmware.setContentType(file.getContentType());  
157 - firmware.setData(ByteBuffer.wrap(bytes));  
158 - firmware.setDataSize((long) bytes.length);  
159 - Firmware savedFirmware = firmwareService.saveFirmware(firmware);  
160 - logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null);  
161 - return savedFirmware; 157 + otaPackage.setChecksumAlgorithm(checksumAlgorithm);
  158 + otaPackage.setChecksum(checksum);
  159 + otaPackage.setFileName(file.getOriginalFilename());
  160 + otaPackage.setContentType(file.getContentType());
  161 + otaPackage.setData(ByteBuffer.wrap(bytes));
  162 + otaPackage.setDataSize((long) bytes.length);
  163 + OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
  164 + logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null);
  165 + return savedOtaPackage;
162 } catch (Exception e) { 166 } catch (Exception e) {
163 - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId); 167 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.UPDATED, e, strOtaPackageId);
164 throw handleException(e); 168 throw handleException(e);
165 } 169 }
166 } 170 }
167 171
168 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 172 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
169 - @RequestMapping(value = "/firmwares", method = RequestMethod.GET) 173 + @RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
170 @ResponseBody 174 @ResponseBody
171 - public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,  
172 - @RequestParam int page,  
173 - @RequestParam(required = false) String textSearch,  
174 - @RequestParam(required = false) String sortProperty,  
175 - @RequestParam(required = false) String sortOrder) throws ThingsboardException { 175 + public PageData<OtaPackageInfo> getOtaPackages(@RequestParam int pageSize,
  176 + @RequestParam int page,
  177 + @RequestParam(required = false) String textSearch,
  178 + @RequestParam(required = false) String sortProperty,
  179 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
176 try { 180 try {
177 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); 181 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
178 - return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink)); 182 + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink));
179 } catch (Exception e) { 183 } catch (Exception e) {
180 throw handleException(e); 184 throw handleException(e);
181 } 185 }
182 } 186 }
183 187
184 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 188 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
185 - @RequestMapping(value = "/firmwares/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET) 189 + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
186 @ResponseBody 190 @ResponseBody
187 - public PageData<FirmwareInfo> getFirmwares(@PathVariable("deviceProfileId") String strDeviceProfileId,  
188 - @PathVariable("type") String strType,  
189 - @PathVariable("hasData") boolean hasData,  
190 - @RequestParam int pageSize,  
191 - @RequestParam int page,  
192 - @RequestParam(required = false) String textSearch,  
193 - @RequestParam(required = false) String sortProperty,  
194 - @RequestParam(required = false) String sortOrder) throws ThingsboardException { 191 + public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId,
  192 + @PathVariable("type") String strType,
  193 + @RequestParam int pageSize,
  194 + @RequestParam int page,
  195 + @RequestParam(required = false) String textSearch,
  196 + @RequestParam(required = false) String sortProperty,
  197 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
195 checkParameter("deviceProfileId", strDeviceProfileId); 198 checkParameter("deviceProfileId", strDeviceProfileId);
196 checkParameter("type", strType); 199 checkParameter("type", strType);
197 try { 200 try {
198 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); 201 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
199 - return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),  
200 - new DeviceProfileId(toUUID(strDeviceProfileId)), FirmwareType.valueOf(strType), hasData, pageLink)); 202 + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),
  203 + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink));
201 } catch (Exception e) { 204 } catch (Exception e) {
202 throw handleException(e); 205 throw handleException(e);
203 } 206 }
204 } 207 }
205 208
206 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 209 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
207 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) 210 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
208 @ResponseBody 211 @ResponseBody
209 - public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {  
210 - checkParameter(FIRMWARE_ID, strFirmwareId); 212 + public void deleteOtaPackage(@PathVariable("otaPackageId") String strOtaPackageId) throws ThingsboardException {
  213 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
211 try { 214 try {
212 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
213 - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE);  
214 - firmwareService.deleteFirmware(getTenantId(), firmwareId);  
215 - logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId); 215 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  216 + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.DELETE);
  217 + otaPackageService.deleteOtaPackage(getTenantId(), otaPackageId);
  218 + logEntityAction(otaPackageId, info, null, ActionType.DELETED, null, strOtaPackageId);
216 } catch (Exception e) { 219 } catch (Exception e) {
217 - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId); 220 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.DELETED, e, strOtaPackageId);
218 throw handleException(e); 221 throw handleException(e);
219 } 222 }
220 } 223 }
  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.action;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import com.fasterxml.jackson.databind.node.ArrayNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import lombok.RequiredArgsConstructor;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.apache.commons.lang3.StringUtils;
  24 +import org.springframework.stereotype.Service;
  25 +import org.thingsboard.server.common.data.DataConstants;
  26 +import org.thingsboard.server.common.data.EntityType;
  27 +import org.thingsboard.server.common.data.HasName;
  28 +import org.thingsboard.server.common.data.HasTenantId;
  29 +import org.thingsboard.server.common.data.User;
  30 +import org.thingsboard.server.common.data.audit.ActionType;
  31 +import org.thingsboard.server.common.data.id.CustomerId;
  32 +import org.thingsboard.server.common.data.id.EntityId;
  33 +import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  35 +import org.thingsboard.server.common.data.kv.DataType;
  36 +import org.thingsboard.server.common.data.kv.KvEntry;
  37 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  38 +import org.thingsboard.server.common.msg.TbMsg;
  39 +import org.thingsboard.server.common.msg.TbMsgDataType;
  40 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  41 +import org.thingsboard.server.queue.util.TbCoreComponent;
  42 +import org.thingsboard.server.service.queue.TbClusterService;
  43 +
  44 +import java.util.List;
  45 +import java.util.Map;
  46 +import java.util.stream.Collectors;
  47 +
  48 +@TbCoreComponent
  49 +@Service
  50 +@RequiredArgsConstructor
  51 +@Slf4j
  52 +public class RuleEngineEntityActionService {
  53 + private final TbClusterService tbClusterService;
  54 +
  55 + private static final ObjectMapper json = new ObjectMapper();
  56 +
  57 + public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId,
  58 + ActionType actionType, User user, Object... additionalInfo) {
  59 + String msgType = null;
  60 + switch (actionType) {
  61 + case ADDED:
  62 + msgType = DataConstants.ENTITY_CREATED;
  63 + break;
  64 + case DELETED:
  65 + msgType = DataConstants.ENTITY_DELETED;
  66 + break;
  67 + case UPDATED:
  68 + msgType = DataConstants.ENTITY_UPDATED;
  69 + break;
  70 + case ASSIGNED_TO_CUSTOMER:
  71 + msgType = DataConstants.ENTITY_ASSIGNED;
  72 + break;
  73 + case UNASSIGNED_FROM_CUSTOMER:
  74 + msgType = DataConstants.ENTITY_UNASSIGNED;
  75 + break;
  76 + case ATTRIBUTES_UPDATED:
  77 + msgType = DataConstants.ATTRIBUTES_UPDATED;
  78 + break;
  79 + case ATTRIBUTES_DELETED:
  80 + msgType = DataConstants.ATTRIBUTES_DELETED;
  81 + break;
  82 + case ALARM_ACK:
  83 + msgType = DataConstants.ALARM_ACK;
  84 + break;
  85 + case ALARM_CLEAR:
  86 + msgType = DataConstants.ALARM_CLEAR;
  87 + break;
  88 + case ALARM_DELETE:
  89 + msgType = DataConstants.ALARM_DELETE;
  90 + break;
  91 + case ASSIGNED_FROM_TENANT:
  92 + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
  93 + break;
  94 + case ASSIGNED_TO_TENANT:
  95 + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
  96 + break;
  97 + case PROVISION_SUCCESS:
  98 + msgType = DataConstants.PROVISION_SUCCESS;
  99 + break;
  100 + case PROVISION_FAILURE:
  101 + msgType = DataConstants.PROVISION_FAILURE;
  102 + break;
  103 + case TIMESERIES_UPDATED:
  104 + msgType = DataConstants.TIMESERIES_UPDATED;
  105 + break;
  106 + case TIMESERIES_DELETED:
  107 + msgType = DataConstants.TIMESERIES_DELETED;
  108 + break;
  109 + case ASSIGNED_TO_EDGE:
  110 + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
  111 + break;
  112 + case UNASSIGNED_FROM_EDGE:
  113 + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
  114 + break;
  115 + }
  116 + if (!StringUtils.isEmpty(msgType)) {
  117 + try {
  118 + TbMsgMetaData metaData = new TbMsgMetaData();
  119 + if (user != null) {
  120 + metaData.putValue("userId", user.getId().toString());
  121 + metaData.putValue("userName", user.getName());
  122 + }
  123 + if (customerId != null && !customerId.isNullUid()) {
  124 + metaData.putValue("customerId", customerId.toString());
  125 + }
  126 + if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) {
  127 + String strCustomerId = extractParameter(String.class, 1, additionalInfo);
  128 + String strCustomerName = extractParameter(String.class, 2, additionalInfo);
  129 + metaData.putValue("assignedCustomerId", strCustomerId);
  130 + metaData.putValue("assignedCustomerName", strCustomerName);
  131 + } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) {
  132 + String strCustomerId = extractParameter(String.class, 1, additionalInfo);
  133 + String strCustomerName = extractParameter(String.class, 2, additionalInfo);
  134 + metaData.putValue("unassignedCustomerId", strCustomerId);
  135 + metaData.putValue("unassignedCustomerName", strCustomerName);
  136 + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
  137 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  138 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  139 + metaData.putValue("assignedFromTenantId", strTenantId);
  140 + metaData.putValue("assignedFromTenantName", strTenantName);
  141 + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
  142 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  143 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  144 + metaData.putValue("assignedToTenantId", strTenantId);
  145 + metaData.putValue("assignedToTenantName", strTenantName);
  146 + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
  147 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  148 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  149 + metaData.putValue("assignedEdgeId", strEdgeId);
  150 + metaData.putValue("assignedEdgeName", strEdgeName);
  151 + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
  152 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  153 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  154 + metaData.putValue("unassignedEdgeId", strEdgeId);
  155 + metaData.putValue("unassignedEdgeName", strEdgeName);
  156 + }
  157 + ObjectNode entityNode;
  158 + if (entity != null) {
  159 + entityNode = json.valueToTree(entity);
  160 + if (entityId.getEntityType() == EntityType.DASHBOARD) {
  161 + entityNode.put("configuration", "");
  162 + }
  163 + } else {
  164 + entityNode = json.createObjectNode();
  165 + if (actionType == ActionType.ATTRIBUTES_UPDATED) {
  166 + String scope = extractParameter(String.class, 0, additionalInfo);
  167 + @SuppressWarnings("unchecked")
  168 + List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
  169 + metaData.putValue(DataConstants.SCOPE, scope);
  170 + if (attributes != null) {
  171 + for (AttributeKvEntry attr : attributes) {
  172 + addKvEntry(entityNode, attr);
  173 + }
  174 + }
  175 + } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
  176 + String scope = extractParameter(String.class, 0, additionalInfo);
  177 + @SuppressWarnings("unchecked")
  178 + List<String> keys = extractParameter(List.class, 1, additionalInfo);
  179 + metaData.putValue(DataConstants.SCOPE, scope);
  180 + ArrayNode attrsArrayNode = entityNode.putArray("attributes");
  181 + if (keys != null) {
  182 + keys.forEach(attrsArrayNode::add);
  183 + }
  184 + } else if (actionType == ActionType.TIMESERIES_UPDATED) {
  185 + @SuppressWarnings("unchecked")
  186 + List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo);
  187 + addTimeseries(entityNode, timeseries);
  188 + } else if (actionType == ActionType.TIMESERIES_DELETED) {
  189 + @SuppressWarnings("unchecked")
  190 + List<String> keys = extractParameter(List.class, 0, additionalInfo);
  191 + if (keys != null) {
  192 + ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries");
  193 + keys.forEach(timeseriesArrayNode::add);
  194 + }
  195 + entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo));
  196 + entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo));
  197 + }
  198 + }
  199 + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
  200 + if (tenantId.isNullUid()) {
  201 + if (entity instanceof HasTenantId) {
  202 + tenantId = ((HasTenantId) entity).getTenantId();
  203 + }
  204 + }
  205 + tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
  206 + } catch (Exception e) {
  207 + log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
  208 + }
  209 + }
  210 + }
  211 +
  212 +
  213 + private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
  214 + T result = null;
  215 + if (additionalInfo != null && additionalInfo.length > index) {
  216 + Object paramObject = additionalInfo[index];
  217 + if (clazz.isInstance(paramObject)) {
  218 + result = clazz.cast(paramObject);
  219 + }
  220 + }
  221 + return result;
  222 + }
  223 +
  224 + private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception {
  225 + if (timeseries != null && !timeseries.isEmpty()) {
  226 + ArrayNode result = entityNode.putArray("timeseries");
  227 + Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream()
  228 + .collect(Collectors.groupingBy(TsKvEntry::getTs));
  229 + for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) {
  230 + ObjectNode element = json.createObjectNode();
  231 + element.put("ts", entry.getKey());
  232 + ObjectNode values = element.putObject("values");
  233 + for (TsKvEntry tsKvEntry : entry.getValue()) {
  234 + addKvEntry(values, tsKvEntry);
  235 + }
  236 + result.add(element);
  237 + }
  238 + }
  239 + }
  240 +
  241 + private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception {
  242 + if (kvEntry.getDataType() == DataType.BOOLEAN) {
  243 + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  244 + } else if (kvEntry.getDataType() == DataType.DOUBLE) {
  245 + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  246 + } else if (kvEntry.getDataType() == DataType.LONG) {
  247 + kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  248 + } else if (kvEntry.getDataType() == DataType.JSON) {
  249 + if (kvEntry.getJsonValue().isPresent()) {
  250 + entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get()));
  251 + }
  252 + } else {
  253 + entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString());
  254 + }
  255 + }
  256 +}
@@ -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 }
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java renamed from application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.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.service.firmware; 16 +package org.thingsboard.server.service.ota;
17 17
18 import com.google.common.util.concurrent.FutureCallback; 18 import com.google.common.util.concurrent.FutureCallback;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
@@ -23,12 +23,10 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; @@ -23,12 +23,10 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
23 import org.thingsboard.server.common.data.DataConstants; 23 import org.thingsboard.server.common.data.DataConstants;
24 import org.thingsboard.server.common.data.Device; 24 import org.thingsboard.server.common.data.Device;
25 import org.thingsboard.server.common.data.DeviceProfile; 25 import org.thingsboard.server.common.data.DeviceProfile;
26 -import org.thingsboard.server.common.data.FirmwareInfo;  
27 -import org.thingsboard.server.common.data.firmware.FirmwareType;  
28 -import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;  
29 -import org.thingsboard.server.common.data.firmware.FirmwareUtil; 26 +import org.thingsboard.server.common.data.OtaPackageInfo;
  27 +import org.thingsboard.server.common.data.StringUtils;
30 import org.thingsboard.server.common.data.id.DeviceId; 28 import org.thingsboard.server.common.data.id.DeviceId;
31 -import org.thingsboard.server.common.data.id.FirmwareId; 29 +import org.thingsboard.server.common.data.id.OtaPackageId;
32 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
33 import org.thingsboard.server.common.data.kv.AttributeKey; 31 import org.thingsboard.server.common.data.kv.AttributeKey;
34 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 32 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@@ -37,13 +35,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -37,13 +35,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
37 import org.thingsboard.server.common.data.kv.LongDataEntry; 35 import org.thingsboard.server.common.data.kv.LongDataEntry;
38 import org.thingsboard.server.common.data.kv.StringDataEntry; 36 import org.thingsboard.server.common.data.kv.StringDataEntry;
39 import org.thingsboard.server.common.data.kv.TsKvEntry; 37 import org.thingsboard.server.common.data.kv.TsKvEntry;
  38 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  39 +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
  40 +import org.thingsboard.server.common.data.ota.OtaPackageUtil;
40 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
41 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
42 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 43 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
43 import org.thingsboard.server.dao.device.DeviceProfileService; 44 import org.thingsboard.server.dao.device.DeviceProfileService;
44 import org.thingsboard.server.dao.device.DeviceService; 45 import org.thingsboard.server.dao.device.DeviceService;
45 -import org.thingsboard.server.dao.firmware.FirmwareService;  
46 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 46 +import org.thingsboard.server.dao.ota.OtaPackageService;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
47 import org.thingsboard.server.queue.TbQueueProducer; 48 import org.thingsboard.server.queue.TbQueueProducer;
48 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 49 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
49 import org.thingsboard.server.queue.provider.TbCoreQueueFactory; 50 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
@@ -58,44 +59,44 @@ import java.util.List; @@ -58,44 +59,44 @@ import java.util.List;
58 import java.util.Set; 59 import java.util.Set;
59 import java.util.UUID; 60 import java.util.UUID;
60 import java.util.function.Consumer; 61 import java.util.function.Consumer;
61 -import java.util.function.Function;  
62 -  
63 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM;  
64 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM_ALGORITHM;  
65 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.SIZE;  
66 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;  
67 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE;  
68 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS;  
69 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION;  
70 -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;  
71 -import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;  
72 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;  
73 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey;  
74 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey; 62 +
  63 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM;
  64 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM;
  65 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
  66 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
  67 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
  68 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
  69 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
  70 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION;
  71 +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
  72 +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
  73 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey;
  74 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTargetTelemetryKey;
  75 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTelemetryKey;
75 76
76 @Slf4j 77 @Slf4j
77 @Service 78 @Service
78 @TbCoreComponent 79 @TbCoreComponent
79 -public class DefaultFirmwareStateService implements FirmwareStateService { 80 +public class DefaultOtaPackageStateService implements OtaPackageStateService {
80 81
81 private final TbClusterService tbClusterService; 82 private final TbClusterService tbClusterService;
82 - private final FirmwareService firmwareService; 83 + private final OtaPackageService otaPackageService;
83 private final DeviceService deviceService; 84 private final DeviceService deviceService;
84 private final DeviceProfileService deviceProfileService; 85 private final DeviceProfileService deviceProfileService;
85 private final RuleEngineTelemetryService telemetryService; 86 private final RuleEngineTelemetryService telemetryService;
86 - private final TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> fwStateMsgProducer; 87 + private final TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> otaPackageStateMsgProducer;
87 88
88 - public DefaultFirmwareStateService(TbClusterService tbClusterService, FirmwareService firmwareService,  
89 - DeviceService deviceService,  
90 - DeviceProfileService deviceProfileService,  
91 - RuleEngineTelemetryService telemetryService,  
92 - TbCoreQueueFactory coreQueueFactory) { 89 + public DefaultOtaPackageStateService(TbClusterService tbClusterService, OtaPackageService otaPackageService,
  90 + DeviceService deviceService,
  91 + DeviceProfileService deviceProfileService,
  92 + RuleEngineTelemetryService telemetryService,
  93 + TbCoreQueueFactory coreQueueFactory) {
93 this.tbClusterService = tbClusterService; 94 this.tbClusterService = tbClusterService;
94 - this.firmwareService = firmwareService; 95 + this.otaPackageService = otaPackageService;
95 this.deviceService = deviceService; 96 this.deviceService = deviceService;
96 this.deviceProfileService = deviceProfileService; 97 this.deviceProfileService = deviceProfileService;
97 this.telemetryService = telemetryService; 98 this.telemetryService = telemetryService;
98 - this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer(); 99 + this.otaPackageStateMsgProducer = coreQueueFactory.createToOtaPackageStateServiceMsgProducer();
99 } 100 }
100 101
101 @Override 102 @Override
@@ -105,14 +106,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -105,14 +106,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
105 } 106 }
106 107
107 private void updateFirmware(Device device, Device oldDevice) { 108 private void updateFirmware(Device device, Device oldDevice) {
108 - FirmwareId newFirmwareId = device.getFirmwareId(); 109 + OtaPackageId newFirmwareId = device.getFirmwareId();
109 if (newFirmwareId == null) { 110 if (newFirmwareId == null) {
110 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); 111 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
111 newFirmwareId = newDeviceProfile.getFirmwareId(); 112 newFirmwareId = newDeviceProfile.getFirmwareId();
112 } 113 }
113 if (oldDevice != null) { 114 if (oldDevice != null) {
114 if (newFirmwareId != null) { 115 if (newFirmwareId != null) {
115 - FirmwareId oldFirmwareId = oldDevice.getFirmwareId(); 116 + OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
116 if (oldFirmwareId == null) { 117 if (oldFirmwareId == null) {
117 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); 118 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
118 oldFirmwareId = oldDeviceProfile.getFirmwareId(); 119 oldFirmwareId = oldDeviceProfile.getFirmwareId();
@@ -132,14 +133,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -132,14 +133,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
132 } 133 }
133 134
134 private void updateSoftware(Device device, Device oldDevice) { 135 private void updateSoftware(Device device, Device oldDevice) {
135 - FirmwareId newSoftwareId = device.getSoftwareId(); 136 + OtaPackageId newSoftwareId = device.getSoftwareId();
136 if (newSoftwareId == null) { 137 if (newSoftwareId == null) {
137 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); 138 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
138 newSoftwareId = newDeviceProfile.getSoftwareId(); 139 newSoftwareId = newDeviceProfile.getSoftwareId();
139 } 140 }
140 if (oldDevice != null) { 141 if (oldDevice != null) {
141 if (newSoftwareId != null) { 142 if (newSoftwareId != null) {
142 - FirmwareId oldSoftwareId = oldDevice.getSoftwareId(); 143 + OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
143 if (oldSoftwareId == null) { 144 if (oldSoftwareId == null) {
144 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); 145 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
145 oldSoftwareId = oldDeviceProfile.getSoftwareId(); 146 oldSoftwareId = oldDeviceProfile.getSoftwareId();
@@ -170,33 +171,20 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -170,33 +171,20 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
170 } 171 }
171 } 172 }
172 173
173 - private void update(TenantId tenantId, DeviceProfile deviceProfile, FirmwareType firmwareType) {  
174 - Function<PageLink, PageData<Device>> getDevicesFunction; 174 + private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) {
175 Consumer<Device> updateConsumer; 175 Consumer<Device> updateConsumer;
176 176
177 - switch (firmwareType) {  
178 - case FIRMWARE:  
179 - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pl);  
180 - break;  
181 - case SOFTWARE:  
182 - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId, deviceProfile.getName(), pl);  
183 - break;  
184 - default:  
185 - log.warn("Unsupported firmware type: [{}]", firmwareType);  
186 - return;  
187 - }  
188 -  
189 if (deviceProfile.getFirmwareId() != null) { 177 if (deviceProfile.getFirmwareId() != null) {
190 long ts = System.currentTimeMillis(); 178 long ts = System.currentTimeMillis();
191 - updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, firmwareType); 179 + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType);
192 } else { 180 } else {
193 - updateConsumer = d -> remove(d, firmwareType); 181 + updateConsumer = d -> remove(d, otaPackageType);
194 } 182 }
195 183
196 PageLink pageLink = new PageLink(100); 184 PageLink pageLink = new PageLink(100);
197 PageData<Device> pageData; 185 PageData<Device> pageData;
198 do { 186 do {
199 - pageData = getDevicesFunction.apply(pageLink); 187 + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId, deviceProfile.getId(), otaPackageType, pageLink);
200 pageData.getData().forEach(updateConsumer); 188 pageData.getData().forEach(updateConsumer);
201 189
202 if (pageData.hasNext()) { 190 if (pageData.hasNext()) {
@@ -206,60 +194,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -206,60 +194,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
206 } 194 }
207 195
208 @Override 196 @Override
209 - public boolean process(ToFirmwareStateServiceMsg msg) { 197 + public boolean process(ToOtaPackageStateServiceMsg msg) {
210 boolean isSuccess = false; 198 boolean isSuccess = false;
211 - FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB())); 199 + OtaPackageId targetOtaPackageId = new OtaPackageId(new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()));
212 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); 200 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
213 TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); 201 TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
214 - FirmwareType firmwareType = FirmwareType.valueOf(msg.getType()); 202 + OtaPackageType firmwareType = OtaPackageType.valueOf(msg.getType());
215 long ts = msg.getTs(); 203 long ts = msg.getTs();
216 204
217 Device device = deviceService.findDeviceById(tenantId, deviceId); 205 Device device = deviceService.findDeviceById(tenantId, deviceId);
218 if (device == null) { 206 if (device == null) {
219 log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); 207 log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId);
220 } else { 208 } else {
221 - FirmwareId currentFirmwareId = FirmwareUtil.getFirmwareId(device, firmwareType);  
222 - if (currentFirmwareId == null) { 209 + OtaPackageId currentOtaPackageId = OtaPackageUtil.getOtaPackageId(device, firmwareType);
  210 + if (currentOtaPackageId == null) {
223 DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()); 211 DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId());
224 - currentFirmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); 212 + currentOtaPackageId = OtaPackageUtil.getOtaPackageId(deviceProfile, firmwareType);
225 } 213 }
226 214
227 - if (targetFirmwareId.equals(currentFirmwareId)) {  
228 - update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts); 215 + if (targetOtaPackageId.equals(currentOtaPackageId)) {
  216 + update(device, otaPackageService.findOtaPackageInfoById(device.getTenantId(), targetOtaPackageId), ts);
229 isSuccess = true; 217 isSuccess = true;
230 } else { 218 } else {
231 - log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); 219 + log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetOtaPackageId, currentOtaPackageId);
232 } 220 }
233 } 221 }
234 return isSuccess; 222 return isSuccess;
235 } 223 }
236 224
237 - private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts, FirmwareType firmwareType) {  
238 - ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder() 225 + private void send(TenantId tenantId, DeviceId deviceId, OtaPackageId firmwareId, long ts, OtaPackageType firmwareType) {
  226 + ToOtaPackageStateServiceMsg msg = ToOtaPackageStateServiceMsg.newBuilder()
239 .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) 227 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
240 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) 228 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
241 .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) 229 .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
242 .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) 230 .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
243 - .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits())  
244 - .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()) 231 + .setOtaPackageIdMSB(firmwareId.getId().getMostSignificantBits())
  232 + .setOtaPackageIdLSB(firmwareId.getId().getLeastSignificantBits())
245 .setType(firmwareType.name()) 233 .setType(firmwareType.name())
246 .setTs(ts) 234 .setTs(ts)
247 .build(); 235 .build();
248 236
249 - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId); 237 + OtaPackageInfo firmware = otaPackageService.findOtaPackageInfoById(tenantId, firmwareId);
250 if (firmware == null) { 238 if (firmware == null) {
251 log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId); 239 log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId);
252 return; 240 return;
253 } 241 }
254 242
255 - TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false);  
256 - fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); 243 + TopicPartitionInfo tpi = new TopicPartitionInfo(otaPackageStateMsgProducer.getDefaultTopic(), null, null, false);
  244 + otaPackageStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
257 245
258 List<TsKvEntry> telemetry = new ArrayList<>(); 246 List<TsKvEntry> telemetry = new ArrayList<>();
259 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle()))); 247 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
260 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion()))); 248 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
261 telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); 249 telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
262 - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.QUEUED.name()))); 250 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
263 251
264 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { 252 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
265 @Override 253 @Override
@@ -275,11 +263,12 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -275,11 +263,12 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
275 } 263 }
276 264
277 265
278 - private void update(Device device, FirmwareInfo firmware, long ts) { 266 + private void update(Device device, OtaPackageInfo otaPackage, long ts) {
279 TenantId tenantId = device.getTenantId(); 267 TenantId tenantId = device.getTenantId();
280 DeviceId deviceId = device.getId(); 268 DeviceId deviceId = device.getId();
  269 + OtaPackageType otaPackageType = otaPackage.getType();
281 270
282 - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.INITIATED.name())); 271 + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
283 272
284 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { 273 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
285 @Override 274 @Override
@@ -294,11 +283,37 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -294,11 +283,37 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
294 }); 283 });
295 284
296 List<AttributeKvEntry> attributes = new ArrayList<>(); 285 List<AttributeKvEntry> attributes = new ArrayList<>();
297 - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));  
298 - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));  
299 - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));  
300 - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));  
301 - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum()))); 286 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
  287 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
  288 + if (otaPackage.hasUrl()) {
  289 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
  290 + List<String> attrToRemove = new ArrayList<>();
  291 +
  292 + if (otaPackage.getDataSize() == null) {
  293 + attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
  294 + } else {
  295 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
  296 + }
  297 +
  298 + if (otaPackage.getChecksumAlgorithm() != null) {
  299 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM));
  300 + } else {
  301 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
  302 + }
  303 +
  304 + if (StringUtils.isEmpty(otaPackage.getChecksum())) {
  305 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM));
  306 + } else {
  307 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
  308 + }
  309 +
  310 + remove(device, otaPackageType, attrToRemove);
  311 + } else {
  312 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
  313 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
  314 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
  315 + remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL)));
  316 + }
302 317
303 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { 318 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
304 @Override 319 @Override
@@ -313,20 +328,24 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -313,20 +328,24 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
313 }); 328 });
314 } 329 }
315 330
316 - private void remove(Device device, FirmwareType firmwareType) {  
317 - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareUtil.getAttributeKeys(firmwareType), 331 + private void remove(Device device, OtaPackageType otaPackageType) {
  332 + remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType));
  333 + }
  334 +
  335 + private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
  336 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys,
318 new FutureCallback<>() { 337 new FutureCallback<>() {
319 @Override 338 @Override
320 public void onSuccess(@Nullable Void tmp) { 339 public void onSuccess(@Nullable Void tmp) {
321 - log.trace("[{}] Success remove target firmware attributes!", device.getId()); 340 + log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
322 Set<AttributeKey> keysToNotify = new HashSet<>(); 341 Set<AttributeKey> keysToNotify = new HashSet<>();
323 - FirmwareUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); 342 + attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
324 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); 343 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
325 } 344 }
326 345
327 @Override 346 @Override
328 public void onFailure(Throwable t) { 347 public void onFailure(Throwable t) {
329 - log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t); 348 + log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
330 } 349 }
331 }); 350 });
332 } 351 }
application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java renamed from application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java
@@ -13,18 +13,18 @@ @@ -13,18 +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.service.firmware; 16 +package org.thingsboard.server.service.ota;
17 17
18 import org.thingsboard.server.common.data.Device; 18 import org.thingsboard.server.common.data.Device;
19 import org.thingsboard.server.common.data.DeviceProfile; 19 import org.thingsboard.server.common.data.DeviceProfile;
20 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 20 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
21 21
22 -public interface FirmwareStateService { 22 +public interface OtaPackageStateService {
23 23
24 void update(Device device, Device oldDevice); 24 void update(Device device, Device oldDevice);
25 25
26 void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged); 26 void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged);
27 27
28 - boolean process(ToFirmwareStateServiceMsg msg); 28 + boolean process(ToOtaPackageStateServiceMsg msg);
29 29
30 } 30 }
@@ -50,7 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP @@ -50,7 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP
50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; 50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; 51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
52 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; 52 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
53 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 53 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; 54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; 55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
56 import org.thingsboard.server.queue.TbQueueConsumer; 56 import org.thingsboard.server.queue.TbQueueConsumer;
@@ -60,7 +60,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; @@ -60,7 +60,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
60 import org.thingsboard.server.queue.util.TbCoreComponent; 60 import org.thingsboard.server.queue.util.TbCoreComponent;
61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService; 61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
62 import org.thingsboard.server.service.edge.EdgeNotificationService; 62 import org.thingsboard.server.service.edge.EdgeNotificationService;
63 -import org.thingsboard.server.service.firmware.FirmwareStateService; 63 +import org.thingsboard.server.service.ota.OtaPackageStateService;
64 import org.thingsboard.server.service.profile.TbDeviceProfileCache; 64 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService; 65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
66 import org.thingsboard.server.service.queue.processing.IdMsgPair; 66 import org.thingsboard.server.service.queue.processing.IdMsgPair;
@@ -75,7 +75,6 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra @@ -75,7 +75,6 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra
75 75
76 import javax.annotation.PostConstruct; 76 import javax.annotation.PostConstruct;
77 import javax.annotation.PreDestroy; 77 import javax.annotation.PreDestroy;
78 -import java.util.ArrayList;  
79 import java.util.List; 78 import java.util.List;
80 import java.util.Optional; 79 import java.util.Optional;
81 import java.util.UUID; 80 import java.util.UUID;
@@ -101,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -101,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
101 @Value("${queue.core.stats.enabled:false}") 100 @Value("${queue.core.stats.enabled:false}")
102 private boolean statsEnabled; 101 private boolean statsEnabled;
103 102
104 - @Value("${queue.core.firmware.pack-interval-ms:60000}") 103 + @Value("${queue.core.ota.pack-interval-ms:60000}")
105 private long firmwarePackInterval; 104 private long firmwarePackInterval;
106 - @Value("${queue.core.firmware.pack-size:100}") 105 + @Value("${queue.core.ota.pack-size:100}")
107 private int firmwarePackSize; 106 private int firmwarePackSize;
108 107
109 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer; 108 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer;
@@ -113,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -113,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
113 private final SubscriptionManagerService subscriptionManagerService; 112 private final SubscriptionManagerService subscriptionManagerService;
114 private final TbCoreDeviceRpcService tbCoreDeviceRpcService; 113 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
115 private final EdgeNotificationService edgeNotificationService; 114 private final EdgeNotificationService edgeNotificationService;
116 - private final FirmwareStateService firmwareStateService; 115 + private final OtaPackageStateService firmwareStateService;
117 private final TbCoreConsumerStats stats; 116 private final TbCoreConsumerStats stats;
118 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer; 117 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
119 - private final TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> firmwareStatesConsumer; 118 + private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
120 119
121 protected volatile ExecutorService usageStatsExecutor; 120 protected volatile ExecutorService usageStatsExecutor;
122 121
@@ -135,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -135,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
135 TbTenantProfileCache tenantProfileCache, 134 TbTenantProfileCache tenantProfileCache,
136 TbApiUsageStateService apiUsageStateService, 135 TbApiUsageStateService apiUsageStateService,
137 EdgeNotificationService edgeNotificationService, 136 EdgeNotificationService edgeNotificationService,
138 - FirmwareStateService firmwareStateService) { 137 + OtaPackageStateService firmwareStateService) {
139 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); 138 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
140 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); 139 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
141 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); 140 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
142 - this.firmwareStatesConsumer = tbCoreQueueFactory.createToFirmwareStateServiceMsgConsumer(); 141 + this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
143 this.stateService = stateService; 142 this.stateService = stateService;
144 this.localSubscriptionService = localSubscriptionService; 143 this.localSubscriptionService = localSubscriptionService;
145 this.subscriptionManagerService = subscriptionManagerService; 144 this.subscriptionManagerService = subscriptionManagerService;
@@ -173,7 +172,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -173,7 +172,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
173 public void onApplicationEvent(ApplicationReadyEvent event) { 172 public void onApplicationEvent(ApplicationReadyEvent event) {
174 super.onApplicationEvent(event); 173 super.onApplicationEvent(event);
175 launchUsageStatsConsumer(); 174 launchUsageStatsConsumer();
176 - launchFirmwareUpdateNotificationConsumer(); 175 + launchOtaPackageUpdateNotificationConsumer();
177 } 176 }
178 177
179 @Override 178 @Override
@@ -361,20 +360,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -361,20 +360,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
361 }); 360 });
362 } 361 }
363 362
364 - private void launchFirmwareUpdateNotificationConsumer() { 363 + private void launchOtaPackageUpdateNotificationConsumer() {
365 long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize; 364 long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize;
366 firmwareStatesExecutor.submit(() -> { 365 firmwareStatesExecutor.submit(() -> {
367 while (!stopped) { 366 while (!stopped) {
368 try { 367 try {
369 - List<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); 368 + List<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration());
370 if (msgs.isEmpty()) { 369 if (msgs.isEmpty()) {
371 continue; 370 continue;
372 } 371 }
373 long timeToSleep = maxProcessingTimeoutPerRecord; 372 long timeToSleep = maxProcessingTimeoutPerRecord;
374 - for (TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg : msgs) { 373 + for (TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg : msgs) {
375 try { 374 try {
376 long startTime = System.currentTimeMillis(); 375 long startTime = System.currentTimeMillis();
377 - boolean isSuccessUpdate = handleFirmwareUpdates(msg); 376 + boolean isSuccessUpdate = handleOtaPackageUpdates(msg);
378 long endTime = System.currentTimeMillis(); 377 long endTime = System.currentTimeMillis();
379 long spentTime = endTime - startTime; 378 long spentTime = endTime - startTime;
380 timeToSleep = timeToSleep - spentTime; 379 timeToSleep = timeToSleep - spentTime;
@@ -402,7 +401,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -402,7 +401,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
402 } 401 }
403 } 402 }
404 } 403 }
405 - log.info("TB Firmware States Consumer stopped."); 404 + log.info("TB Ota Package States Consumer stopped.");
406 }); 405 });
407 } 406 }
408 407
@@ -410,7 +409,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -410,7 +409,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
410 statsService.process(msg, callback); 409 statsService.process(msg, callback);
411 } 410 }
412 411
413 - private boolean handleFirmwareUpdates(TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg) { 412 + private boolean handleOtaPackageUpdates(TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg) {
414 return firmwareStateService.process(msg.getValue()); 413 return firmwareStateService.process(msg.getValue());
415 } 414 }
416 415
@@ -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 {
@@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService { @@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService {
157 resourceService.deleteResourcesByTenantId(tenantId); 157 resourceService.deleteResourcesByTenantId(tenantId);
158 } 158 }
159 159
  160 + @Override
  161 + public long sumDataSizeByTenantId(TenantId tenantId) {
  162 + return resourceService.sumDataSizeByTenantId(tenantId);
  163 + }
  164 +
160 private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) { 165 private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
161 Comparator<LwM2mObject> comparator; 166 Comparator<LwM2mObject> comparator;
162 if ("name".equals(sortProperty)) { 167 if ("name".equals(sortProperty)) {
@@ -171,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -171,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService {
171 try { 176 try {
172 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); 177 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
173 List<ObjectModel> objectModels = 178 List<ObjectModel> objectModels =
174 - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); 179 + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
175 if (objectModels.size() == 0) { 180 if (objectModels.size() == 0) {
176 return null; 181 return null;
177 } else { 182 } else {
@@ -55,4 +55,5 @@ public interface TbResourceService { @@ -55,4 +55,5 @@ public interface TbResourceService {
55 55
56 void deleteResourcesByTenantId(TenantId tenantId); 56 void deleteResourcesByTenantId(TenantId tenantId);
57 57
  58 + long sumDataSizeByTenantId(TenantId tenantId);
58 } 59 }
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Customer; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Customer;
30 import org.thingsboard.server.common.data.Device; 30 import org.thingsboard.server.common.data.Device;
31 import org.thingsboard.server.common.data.DeviceProfile; 31 import org.thingsboard.server.common.data.DeviceProfile;
32 import org.thingsboard.server.common.data.EntityView; 32 import org.thingsboard.server.common.data.EntityView;
33 -import org.thingsboard.server.common.data.FirmwareInfo; 33 +import org.thingsboard.server.common.data.OtaPackageInfo;
34 import org.thingsboard.server.common.data.TbResourceInfo; 34 import org.thingsboard.server.common.data.TbResourceInfo;
35 import org.thingsboard.server.common.data.Tenant; 35 import org.thingsboard.server.common.data.Tenant;
36 import org.thingsboard.server.common.data.User; 36 import org.thingsboard.server.common.data.User;
@@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
46 import org.thingsboard.server.common.data.id.EntityId; 46 import org.thingsboard.server.common.data.id.EntityId;
47 import org.thingsboard.server.common.data.id.EntityIdFactory; 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
48 import org.thingsboard.server.common.data.id.EntityViewId; 48 import org.thingsboard.server.common.data.id.EntityViewId;
49 -import org.thingsboard.server.common.data.id.FirmwareId; 49 +import org.thingsboard.server.common.data.id.OtaPackageId;
50 import org.thingsboard.server.common.data.id.RuleChainId; 50 import org.thingsboard.server.common.data.id.RuleChainId;
51 import org.thingsboard.server.common.data.id.RuleNodeId; 51 import org.thingsboard.server.common.data.id.RuleNodeId;
52 import org.thingsboard.server.common.data.id.TbResourceId; 52 import org.thingsboard.server.common.data.id.TbResourceId;
@@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService;
63 import org.thingsboard.server.dao.edge.EdgeService; 63 import org.thingsboard.server.dao.edge.EdgeService;
64 import org.thingsboard.server.dao.entityview.EntityViewService; 64 import org.thingsboard.server.dao.entityview.EntityViewService;
65 import org.thingsboard.server.dao.exception.IncorrectParameterException; 65 import org.thingsboard.server.dao.exception.IncorrectParameterException;
66 -import org.thingsboard.server.dao.firmware.FirmwareService; 66 +import org.thingsboard.server.dao.ota.OtaPackageService;
67 import org.thingsboard.server.dao.resource.ResourceService; 67 import org.thingsboard.server.dao.resource.ResourceService;
68 import org.thingsboard.server.dao.rule.RuleChainService; 68 import org.thingsboard.server.dao.rule.RuleChainService;
69 import org.thingsboard.server.dao.tenant.TenantService; 69 import org.thingsboard.server.dao.tenant.TenantService;
@@ -135,7 +135,7 @@ public class AccessValidator { @@ -135,7 +135,7 @@ public class AccessValidator {
135 protected ResourceService resourceService; 135 protected ResourceService resourceService;
136 136
137 @Autowired 137 @Autowired
138 - protected FirmwareService firmwareService; 138 + protected OtaPackageService otaPackageService;
139 139
140 private ExecutorService executor; 140 private ExecutorService executor;
141 141
@@ -232,8 +232,8 @@ public class AccessValidator { @@ -232,8 +232,8 @@ public class AccessValidator {
232 case TB_RESOURCE: 232 case TB_RESOURCE:
233 validateResource(currentUser, operation, entityId, callback); 233 validateResource(currentUser, operation, entityId, callback);
234 return; 234 return;
235 - case FIRMWARE:  
236 - validateFirmware(currentUser, operation, entityId, callback); 235 + case OTA_PACKAGE:
  236 + validateOtaPackage(currentUser, operation, entityId, callback);
237 return; 237 return;
238 default: 238 default:
239 //TODO: add support of other entities 239 //TODO: add support of other entities
@@ -300,20 +300,20 @@ public class AccessValidator { @@ -300,20 +300,20 @@ public class AccessValidator {
300 } 300 }
301 } 301 }
302 302
303 - private void validateFirmware(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { 303 + private void validateOtaPackage(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
304 if (currentUser.isSystemAdmin()) { 304 if (currentUser.isSystemAdmin()) {
305 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); 305 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
306 } else { 306 } else {
307 - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(currentUser.getTenantId(), new FirmwareId(entityId.getId()));  
308 - if (firmware == null) {  
309 - callback.onSuccess(ValidationResult.entityNotFound("Firmware with requested id wasn't found!")); 307 + OtaPackageInfo otaPackage = otaPackageService.findOtaPackageInfoById(currentUser.getTenantId(), new OtaPackageId(entityId.getId()));
  308 + if (otaPackage == null) {
  309 + callback.onSuccess(ValidationResult.entityNotFound("OtaPackage with requested id wasn't found!"));
310 } else { 310 } else {
311 try { 311 try {
312 - accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware); 312 + accessControlService.checkPermission(currentUser, Resource.OTA_PACKAGE, operation, entityId, otaPackage);
313 } catch (ThingsboardException e) { 313 } catch (ThingsboardException e) {
314 callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); 314 callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
315 } 315 }
316 - callback.onSuccess(ValidationResult.ok(firmware)); 316 + callback.onSuccess(ValidationResult.ok(otaPackage));
317 } 317 }
318 } 318 }
319 } 319 }