Commit 5626c30f2f7d64f8838118a2a3db9e83f98ea933

Authored by YevhenBondarenko
2 parents 489c6777 dbc3ef95

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

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

Too many changes to show.

To preserve performance only 57 of 139 files are displayed.

... ... @@ -223,6 +223,27 @@
223 223 "funcBody": null,
224 224 "usePostProcessing": null,
225 225 "postFuncBody": null
  226 + },
  227 + {
  228 + "name": "fw_url",
  229 + "type": "attribute",
  230 + "label": "fw_url",
  231 + "color": "#e91e63",
  232 + "settings": {
  233 + "columnWidth": "0px",
  234 + "useCellStyleFunction": false,
  235 + "cellStyleFunction": "",
  236 + "useCellContentFunction": false,
  237 + "cellContentFunction": "",
  238 + "defaultColumnVisibility": "hidden",
  239 + "columnSelectionToDisplay": "disabled"
  240 + },
  241 + "_hash": 0.4204673738685043,
  242 + "units": null,
  243 + "decimals": null,
  244 + "funcBody": null,
  245 + "usePostProcessing": null,
  246 + "postFuncBody": null
226 247 }
227 248 ]
228 249 }
... ... @@ -249,23 +270,23 @@
249 270 "icon": "edit",
250 271 "type": "customPretty",
251 272 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
252   - "customCss": "",
  273 + "customCss": "form {\n min-width: 300px !important;\n}",
253 274 "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
254 275 "customResources": [],
255 276 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
256 277 },
257 278 {
258   - "name": "Download firware",
  279 + "name": "Download firmware",
259 280 "icon": "file_download",
260 281 "type": "custom",
261   - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  282 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
262 283 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
263 284 },
264 285 {
265   - "name": "Copy checksum",
  286 + "name": "Copy checksum/URL",
266 287 "icon": "content_copy",
267 288 "type": "custom",
268   - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  289 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
269 290 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
270 291 }
271 292 ]
... ... @@ -997,6 +1018,27 @@
997 1018 "funcBody": null,
998 1019 "usePostProcessing": null,
999 1020 "postFuncBody": null
  1021 + },
  1022 + {
  1023 + "name": "fw_url",
  1024 + "type": "attribute",
  1025 + "label": "fw_url",
  1026 + "color": "#e91e63",
  1027 + "settings": {
  1028 + "columnWidth": "0px",
  1029 + "useCellStyleFunction": false,
  1030 + "cellStyleFunction": "",
  1031 + "useCellContentFunction": false,
  1032 + "cellContentFunction": "",
  1033 + "defaultColumnVisibility": "hidden",
  1034 + "columnSelectionToDisplay": "disabled"
  1035 + },
  1036 + "_hash": 0.4204673738685043,
  1037 + "units": null,
  1038 + "decimals": null,
  1039 + "funcBody": null,
  1040 + "usePostProcessing": null,
  1041 + "postFuncBody": null
1000 1042 }
1001 1043 ]
1002 1044 }
... ... @@ -1023,23 +1065,23 @@
1023 1065 "icon": "edit",
1024 1066 "type": "customPretty",
1025 1067 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1026   - "customCss": "",
  1068 + "customCss": "form {\n min-width: 300px !important;\n}",
1027 1069 "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
1028 1070 "customResources": [],
1029 1071 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1030 1072 },
1031 1073 {
1032   - "name": "Download firware",
  1074 + "name": "Download firmware",
1033 1075 "icon": "file_download",
1034 1076 "type": "custom",
1035   - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1077 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1036 1078 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1037 1079 },
1038 1080 {
1039   - "name": "Copy checksum",
  1081 + "name": "Copy checksum/URL",
1040 1082 "icon": "content_copy",
1041 1083 "type": "custom",
1042   - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1084 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1043 1085 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1044 1086 }
1045 1087 ]
... ... @@ -1273,6 +1315,27 @@
1273 1315 "funcBody": null,
1274 1316 "usePostProcessing": null,
1275 1317 "postFuncBody": null
  1318 + },
  1319 + {
  1320 + "name": "fw_url",
  1321 + "type": "attribute",
  1322 + "label": "fw_url",
  1323 + "color": "#e91e63",
  1324 + "settings": {
  1325 + "columnWidth": "0px",
  1326 + "useCellStyleFunction": false,
  1327 + "cellStyleFunction": "",
  1328 + "useCellContentFunction": false,
  1329 + "cellContentFunction": "",
  1330 + "defaultColumnVisibility": "hidden",
  1331 + "columnSelectionToDisplay": "disabled"
  1332 + },
  1333 + "_hash": 0.4204673738685043,
  1334 + "units": null,
  1335 + "decimals": null,
  1336 + "funcBody": null,
  1337 + "usePostProcessing": null,
  1338 + "postFuncBody": null
1276 1339 }
1277 1340 ]
1278 1341 }
... ... @@ -1299,23 +1362,23 @@
1299 1362 "icon": "edit",
1300 1363 "type": "customPretty",
1301 1364 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1302   - "customCss": "",
  1365 + "customCss": "form {\n min-width: 300px !important;\n}",
1303 1366 "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
1304 1367 "customResources": [],
1305 1368 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1306 1369 },
1307 1370 {
1308   - "name": "Download firware",
  1371 + "name": "Download firmware",
1309 1372 "icon": "file_download",
1310 1373 "type": "custom",
1311   - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1374 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1312 1375 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1313 1376 },
1314 1377 {
1315   - "name": "Copy checksum",
  1378 + "name": "Copy checksum/URL",
1316 1379 "icon": "content_copy",
1317 1380 "type": "custom",
1318   - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1381 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1319 1382 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1320 1383 }
1321 1384 ]
... ... @@ -1549,6 +1612,27 @@
1549 1612 "funcBody": null,
1550 1613 "usePostProcessing": null,
1551 1614 "postFuncBody": null
  1615 + },
  1616 + {
  1617 + "name": "fw_url",
  1618 + "type": "attribute",
  1619 + "label": "fw_url",
  1620 + "color": "#e91e63",
  1621 + "settings": {
  1622 + "columnWidth": "0px",
  1623 + "useCellStyleFunction": false,
  1624 + "cellStyleFunction": "",
  1625 + "useCellContentFunction": false,
  1626 + "cellContentFunction": "",
  1627 + "defaultColumnVisibility": "hidden",
  1628 + "columnSelectionToDisplay": "disabled"
  1629 + },
  1630 + "_hash": 0.4204673738685043,
  1631 + "units": null,
  1632 + "decimals": null,
  1633 + "funcBody": null,
  1634 + "usePostProcessing": null,
  1635 + "postFuncBody": null
1552 1636 }
1553 1637 ]
1554 1638 }
... ... @@ -1575,23 +1659,23 @@
1575 1659 "icon": "edit",
1576 1660 "type": "customPretty",
1577 1661 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1578   - "customCss": "",
  1662 + "customCss": "form {\n min-width: 300px !important;\n}",
1579 1663 "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
1580 1664 "customResources": [],
1581 1665 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1582 1666 },
1583 1667 {
1584   - "name": "Download firware",
  1668 + "name": "Download firmware",
1585 1669 "icon": "file_download",
1586 1670 "type": "custom",
1587   - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1671 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1588 1672 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1589 1673 },
1590 1674 {
1591   - "name": "Copy checksum",
  1675 + "name": "Copy checksum/URL",
1592 1676 "icon": "content_copy",
1593 1677 "type": "custom",
1594   - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1678 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1595 1679 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1596 1680 }
1597 1681 ]
... ... @@ -1825,6 +1909,27 @@
1825 1909 "funcBody": null,
1826 1910 "usePostProcessing": null,
1827 1911 "postFuncBody": null
  1912 + },
  1913 + {
  1914 + "name": "fw_url",
  1915 + "type": "attribute",
  1916 + "label": "fw_url",
  1917 + "color": "#e91e63",
  1918 + "settings": {
  1919 + "columnWidth": "0px",
  1920 + "useCellStyleFunction": false,
  1921 + "cellStyleFunction": "",
  1922 + "useCellContentFunction": false,
  1923 + "cellContentFunction": "",
  1924 + "defaultColumnVisibility": "hidden",
  1925 + "columnSelectionToDisplay": "disabled"
  1926 + },
  1927 + "_hash": 0.4204673738685043,
  1928 + "units": null,
  1929 + "decimals": null,
  1930 + "funcBody": null,
  1931 + "usePostProcessing": null,
  1932 + "postFuncBody": null
1828 1933 }
1829 1934 ]
1830 1935 }
... ... @@ -1851,23 +1956,23 @@
1851 1956 "icon": "edit",
1852 1957 "type": "customPretty",
1853 1958 "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
1854   - "customCss": "",
  1959 + "customCss": "form {\n min-width: 300px !important;\n}",
1855 1960 "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
1856 1961 "customResources": [],
1857 1962 "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
1858 1963 },
1859 1964 {
1860   - "name": "Download firware",
  1965 + "name": "Download firmware",
1861 1966 "icon": "file_download",
1862 1967 "type": "custom",
1863   - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1968 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1864 1969 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1865 1970 },
1866 1971 {
1867   - "name": "Copy checksum",
  1972 + "name": "Copy checksum/URL",
1868 1973 "icon": "content_copy",
1869 1974 "type": "custom",
1870   - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1975 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}",
1871 1976 "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
1872 1977 }
1873 1978 ]
... ... @@ -1936,7 +2041,7 @@
1936 2041 }
1937 2042 },
1938 2043 "device_firmware_history": {
1939   - "name": "Device Firmware history",
  2044 + "name": "Firmware history: ${entityName}",
1940 2045 "root": false,
1941 2046 "layouts": {
1942 2047 "main": {
... ... @@ -2379,7 +2484,8 @@
2379 2484 "titleColor": "rgba(0,0,0,0.870588)",
2380 2485 "showFilters": true,
2381 2486 "showDashboardLogo": false,
2382   - "dashboardLogoUrl": null
  2487 + "dashboardLogoUrl": null,
  2488 + "showUpdateDashboardImage": false
2383 2489 }
2384 2490 },
2385 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 +}
\ No newline at end of file
... ...
... ... @@ -17,9 +17,9 @@
17 17 "sizeY": 5.5,
18 18 "resources": [],
19 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 23 "dataKeySettingsSchema": "{}\n",
24 24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
25 25 }
... ...
... ... @@ -79,6 +79,68 @@ CREATE TABLE IF NOT EXISTS ota_package (
79 79 CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
80 80 );
81 81
  82 +CREATE TABLE IF NOT EXISTS oauth2_params (
  83 + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY,
  84 + enabled boolean,
  85 + tenant_id uuid,
  86 + created_time bigint NOT NULL
  87 +);
  88 +
  89 +CREATE TABLE IF NOT EXISTS oauth2_registration (
  90 + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY,
  91 + oauth2_params_id uuid NOT NULL,
  92 + created_time bigint NOT NULL,
  93 + additional_info varchar,
  94 + client_id varchar(255),
  95 + client_secret varchar(255),
  96 + authorization_uri varchar(255),
  97 + token_uri varchar(255),
  98 + scope varchar(255),
  99 + platforms varchar(255),
  100 + user_info_uri varchar(255),
  101 + user_name_attribute_name varchar(255),
  102 + jwk_set_uri varchar(255),
  103 + client_authentication_method varchar(255),
  104 + login_button_label varchar(255),
  105 + login_button_icon varchar(255),
  106 + allow_user_creation boolean,
  107 + activate_user boolean,
  108 + type varchar(31),
  109 + basic_email_attribute_key varchar(31),
  110 + basic_first_name_attribute_key varchar(31),
  111 + basic_last_name_attribute_key varchar(31),
  112 + basic_tenant_name_strategy varchar(31),
  113 + basic_tenant_name_pattern varchar(255),
  114 + basic_customer_name_pattern varchar(255),
  115 + basic_default_dashboard_name varchar(255),
  116 + basic_always_full_screen boolean,
  117 + custom_url varchar(255),
  118 + custom_username varchar(255),
  119 + custom_password varchar(255),
  120 + custom_send_token boolean,
  121 + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE
  122 +);
  123 +
  124 +CREATE TABLE IF NOT EXISTS oauth2_domain (
  125 + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY,
  126 + oauth2_params_id uuid NOT NULL,
  127 + created_time bigint NOT NULL,
  128 + domain_name varchar(255),
  129 + domain_scheme varchar(31),
  130 + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  131 + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme)
  132 +);
  133 +
  134 +CREATE TABLE IF NOT EXISTS oauth2_mobile (
  135 + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY,
  136 + oauth2_params_id uuid NOT NULL,
  137 + created_time bigint NOT NULL,
  138 + pkg_name varchar(255),
  139 + callback_url_scheme varchar(255),
  140 + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
  141 + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name)
  142 +);
  143 +
82 144 ALTER TABLE dashboard
83 145 ADD COLUMN IF NOT EXISTS image varchar(1000000);
84 146
... ...
... ... @@ -204,6 +204,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
204 204 syncSessionSet.add(key);
205 205 }
206 206 });
  207 + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions);
207 208 syncSessionSet.forEach(rpcSubscriptions::remove);
208 209 }
209 210
... ... @@ -517,7 +518,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
517 518 } else {
518 519 SessionInfoMetaData sessionMD = sessions.get(sessionId);
519 520 if (sessionMD == null) {
520   - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId()));
  521 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
521 522 }
522 523 sessionMD.setSubscribedToAttributes(true);
523 524 log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
... ... @@ -538,7 +539,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
538 539 } else {
539 540 SessionInfoMetaData sessionMD = sessions.get(sessionId);
540 541 if (sessionMD == null) {
541   - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId()));
  542 + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
542 543 }
543 544 sessionMD.setSubscribedToRPC(true);
544 545 log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
... ...
... ... @@ -37,6 +37,8 @@ import org.springframework.util.StringUtils;
37 37 import org.springframework.web.util.UriComponents;
38 38 import org.springframework.web.util.UriComponentsBuilder;
39 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 42 import org.thingsboard.server.utils.MiscUtils;
41 43
42 44 import javax.servlet.http.HttpServletRequest;
... ... @@ -46,12 +48,13 @@ import java.security.NoSuchAlgorithmException;
46 48 import java.util.Base64;
47 49 import java.util.HashMap;
48 50 import java.util.Map;
  51 +import java.util.UUID;
49 52
50 53 @Service
51 54 @Slf4j
52 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 58 private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
56 59 private static final char PATH_DELIMITER = '/';
57 60
... ... @@ -63,6 +66,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
63 66 @Autowired
64 67 private ClientRegistrationRepository clientRegistrationRepository;
65 68
  69 + @Autowired
  70 + private OAuth2Service oAuth2Service;
  71 +
66 72 @Autowired(required = false)
67 73 private OAuth2Configuration oauth2Configuration;
68 74
... ... @@ -71,7 +77,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
71 77 public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
72 78 String registrationId = this.resolveRegistrationId(request);
73 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 84 @Override
... ... @@ -80,7 +87,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
80 87 return null;
81 88 }
82 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 94 private String getAction(HttpServletRequest request, String defaultAction) {
... ... @@ -91,8 +99,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
91 99 return action;
92 100 }
93 101
  102 + private String getAppPackage(HttpServletRequest request) {
  103 + return request.getParameter("pkg");
  104 + }
  105 +
94 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 108 if (registrationId == null) {
97 109 return null;
98 110 }
... ... @@ -104,6 +116,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
104 116
105 117 Map<String, Object> attributes = new HashMap<>();
106 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 128 OAuth2AuthorizationRequest.Builder builder;
109 129 if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
... ...
... ... @@ -22,12 +22,15 @@ import org.springframework.security.access.prepost.PreAuthorize;
22 22 import org.springframework.web.bind.annotation.RequestBody;
23 23 import org.springframework.web.bind.annotation.RequestMapping;
24 24 import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
25 26 import org.springframework.web.bind.annotation.ResponseBody;
26 27 import org.springframework.web.bind.annotation.ResponseStatus;
27 28 import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.StringUtils;
28 30 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 31 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
30   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
  32 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
  33 +import org.thingsboard.server.common.data.oauth2.PlatformType;
31 34 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
32 35 import org.thingsboard.server.queue.util.TbCoreComponent;
33 36 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -49,7 +52,9 @@ public class OAuth2Controller extends BaseController {
49 52
50 53 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
51 54 @ResponseBody
52   - public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
  55 + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request,
  56 + @RequestParam(required = false) String pkgName,
  57 + @RequestParam(required = false) String platform) throws ThingsboardException {
53 58 try {
54 59 if (log.isDebugEnabled()) {
55 60 log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
... ... @@ -59,7 +64,13 @@ public class OAuth2Controller extends BaseController {
59 64 log.debug("Header: {} {}", header, request.getHeader(header));
60 65 }
61 66 }
62   - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request));
  67 + PlatformType platformType = null;
  68 + if (StringUtils.isNotEmpty(platform)) {
  69 + try {
  70 + platformType = PlatformType.valueOf(platform);
  71 + } catch (Exception e) {}
  72 + }
  73 + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType);
63 74 } catch (Exception e) {
64 75 throw handleException(e);
65 76 }
... ... @@ -68,10 +79,10 @@ public class OAuth2Controller extends BaseController {
68 79 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
69 80 @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json")
70 81 @ResponseBody
71   - public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException {
  82 + public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException {
72 83 try {
73 84 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
74   - return oAuth2Service.findOAuth2Params();
  85 + return oAuth2Service.findOAuth2Info();
75 86 } catch (Exception e) {
76 87 throw handleException(e);
77 88 }
... ... @@ -80,11 +91,11 @@ public class OAuth2Controller extends BaseController {
80 91 @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
81 92 @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
82 93 @ResponseStatus(value = HttpStatus.OK)
83   - public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException {
  94 + public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException {
84 95 try {
85 96 accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE);
86   - oAuth2Service.saveOAuth2Params(oauth2Params);
87   - return oAuth2Service.findOAuth2Params();
  97 + oAuth2Service.saveOAuth2Info(oauth2Info);
  98 + return oAuth2Service.findOAuth2Info();
88 99 } catch (Exception e) {
89 100 throw handleException(e);
90 101 }
... ...
... ... @@ -199,6 +199,7 @@ public class ThingsboardInstallService {
199 199 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
200 200
201 201 dataUpdateService.updateData("3.2.2");
  202 + systemDataLoaderService.createOAuth2Templates();
202 203
203 204 log.info("Updating system data...");
204 205 systemDataLoaderService.updateSystemWidgets();
... ...
... ... @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.context.annotation.Profile;
25 25 import org.springframework.stereotype.Service;
  26 +import org.thingsboard.common.util.JacksonUtil;
26 27 import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
27 28 import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
28 29 import org.thingsboard.server.common.data.EntityView;
... ... @@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId;
35 36 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
36 37 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
37 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 41 import org.thingsboard.server.common.data.page.PageData;
39 42 import org.thingsboard.server.common.data.page.PageLink;
40 43 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao;
45 48 import org.thingsboard.server.dao.alarm.AlarmService;
46 49 import org.thingsboard.server.dao.entity.EntityService;
47 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 53 import org.thingsboard.server.dao.rule.RuleChainService;
49 54 import org.thingsboard.server.dao.tenant.TenantService;
50 55 import org.thingsboard.server.dao.timeseries.TimeseriesService;
51   -import org.thingsboard.common.util.JacksonUtil;
52 56 import org.thingsboard.server.service.install.InstallScripts;
53 57
54 58 import java.util.ArrayList;
... ... @@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
88 92 @Autowired
89 93 private AlarmDao alarmDao;
90 94
  95 + @Autowired
  96 + private OAuth2Service oAuth2Service;
  97 +
91 98 @Override
92 99 public void updateData(String fromVersion) throws Exception {
93 100 switch (fromVersion) {
... ... @@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
107 114 log.info("Updating data from version 3.2.2 to 3.3.0 ...");
108 115 tenantsDefaultEdgeRuleChainUpdater.updateEntities(null);
109 116 tenantsAlarmsCustomerUpdater.updateEntities(null);
  117 + updateOAuth2Params();
110 118 break;
111 119 default:
112 120 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
... ... @@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService {
362 370 }
363 371 }
364 372
  373 + private void updateOAuth2Params() {
  374 + try {
  375 + OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params();
  376 + if (!oauth2ClientsParams.getDomainsParams().isEmpty()) {
  377 + log.info("Updating OAuth2 parameters ...");
  378 + OAuth2Info oAuth2Info = OAuth2Utils.clientParamsToOAuth2Info(oauth2ClientsParams);
  379 + oAuth2Service.saveOAuth2Info(oAuth2Info);
  380 + oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList()));
  381 + log.info("Successfully updated OAuth2 parameters!");
  382 + }
  383 + }
  384 + catch (Exception e) {
  385 + log.error("Failed to update OAuth2 parameters", e);
  386 + }
  387 + }
  388 +
365 389 }
... ...
... ... @@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService {
72 72 if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
73 73 try {
74 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 76 if (!objectModels.isEmpty()) {
77 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 80 String name = objectModel.name;
81 81 resource.setResourceKey(resourceKey);
82 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 85 resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name);
86 86 } else {
... ... @@ -176,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService {
176 176 try {
177 177 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
178 178 List<ObjectModel> objectModels =
179   - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
  179 + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
180 180 if (objectModels.size() == 0) {
181 181 return null;
182 182 } else {
... ...
... ... @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
33 33 import org.thingsboard.server.common.data.id.DashboardId;
34 34 import org.thingsboard.server.common.data.id.IdBased;
35 35 import org.thingsboard.server.common.data.id.TenantId;
36   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
37 36 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  37 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
38 38 import org.thingsboard.server.common.data.page.PageData;
39 39 import org.thingsboard.server.common.data.page.PageLink;
40 40 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
... ... @@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper {
93 93
94 94 private final Lock userCreationLock = new ReentrantLock();
95 95
96   - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
  96 + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) {
97 97
98   - OAuth2MapperConfig config = clientRegistration.getMapperConfig();
  98 + OAuth2MapperConfig config = registration.getMapperConfig();
99 99
100 100 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
101 101
... ... @@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper {
139 139 }
140 140 }
141 141
142   - if (clientRegistration.getAdditionalInfo() != null &&
143   - clientRegistration.getAdditionalInfo().has("providerName")) {
144   - additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText());
  142 + if (registration.getAdditionalInfo() != null &&
  143 + registration.getAdditionalInfo().has("providerName")) {
  144 + additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText());
145 145 }
146 146
147 147 user.setAdditionalInfo(additionalInfo);
... ...
... ... @@ -18,8 +18,8 @@ package org.thingsboard.server.service.security.auth.oauth2;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
20 20 import org.springframework.stereotype.Service;
21   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
22 21 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  22 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
23 23 import org.thingsboard.server.dao.oauth2.OAuth2User;
24 24 import org.thingsboard.server.service.security.model.SecurityUser;
25 25
... ... @@ -30,12 +30,12 @@ import java.util.Map;
30 30 public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
31 31
32 32 @Override
33   - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
34   - OAuth2MapperConfig config = clientRegistration.getMapperConfig();
  33 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  34 + OAuth2MapperConfig config = registration.getMapperConfig();
35 35 Map<String, Object> attributes = token.getPrincipal().getAttributes();
36 36 String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
37 37 OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
38 38
39   - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
  39 + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
40 40 }
41 41 }
... ...
... ... @@ -23,9 +23,9 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
23 23 import org.springframework.stereotype.Service;
24 24 import org.springframework.util.StringUtils;
25 25 import org.springframework.web.client.RestTemplate;
26   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
27 26 import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
28 27 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  28 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
29 29 import org.thingsboard.server.dao.oauth2.OAuth2User;
30 30 import org.thingsboard.server.service.security.model.SecurityUser;
31 31
... ... @@ -39,10 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
39 39 private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
40 40
41 41 @Override
42   - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
43   - OAuth2MapperConfig config = clientRegistration.getMapperConfig();
  42 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  43 + OAuth2MapperConfig config = registration.getMapperConfig();
44 44 OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
45   - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
  45 + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
46 46 }
47 47
48 48 private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {
... ...
... ... @@ -23,8 +23,8 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
23 23 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.web.client.RestTemplate;
26   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
27 26 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  27 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
28 28 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
29 29 import org.thingsboard.server.dao.oauth2.OAuth2User;
30 30 import org.thingsboard.server.service.security.model.SecurityUser;
... ... @@ -46,13 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
46 46 private OAuth2Configuration oAuth2Configuration;
47 47
48 48 @Override
49   - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
50   - OAuth2MapperConfig config = clientRegistration.getMapperConfig();
  49 + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
  50 + OAuth2MapperConfig config = registration.getMapperConfig();
51 51 Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
52 52 String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
53 53 Map<String, Object> attributes = token.getPrincipal().getAttributes();
54 54 OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
55   - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration);
  55 + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration);
56 56 }
57 57
58 58 private synchronized String getEmail(String emailUrl, String oauth2Token) {
... ...
... ... @@ -16,9 +16,10 @@
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
18 18 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
19   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
  20 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
20 21 import org.thingsboard.server.service.security.model.SecurityUser;
21 22
22 23 public interface OAuth2ClientMapper {
23   - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration);
  24 + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration);
24 25 }
... ...
... ... @@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2;
18 18 import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20 20 import org.springframework.security.core.AuthenticationException;
  21 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
21 22 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
22 23 import org.springframework.stereotype.Component;
  24 +import org.thingsboard.server.common.data.StringUtils;
23 25 import org.thingsboard.server.common.data.id.CustomerId;
24 26 import org.thingsboard.server.common.data.id.EntityId;
25 27 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -51,9 +53,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
51 53 public void onAuthenticationFailure(HttpServletRequest request,
52 54 HttpServletResponse response, AuthenticationException exception)
53 55 throws IOException, ServletException {
54   - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  56 + String baseUrl;
  57 + String errorPrefix;
  58 + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request);
  59 + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME);
  60 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  61 + baseUrl = callbackUrlScheme + ":";
  62 + errorPrefix = "/?error=";
  63 + } else {
  64 + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  65 + errorPrefix = "/login?loginError=";
  66 + }
55 67 httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
56   - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
  68 + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
57 69 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
58 70 }
59 71 }
... ...
... ... @@ -20,12 +20,14 @@ import org.springframework.security.core.Authentication;
20 20 import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
21 21 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
22 22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
  23 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
23 24 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
24 25 import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.data.StringUtils;
25 27 import org.thingsboard.server.common.data.id.CustomerId;
26 28 import org.thingsboard.server.common.data.id.EntityId;
27 29 import org.thingsboard.server.common.data.id.TenantId;
28   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  30 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
29 31 import org.thingsboard.server.common.data.security.model.JwtToken;
30 32 import org.thingsboard.server.dao.oauth2.OAuth2Service;
31 33 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
... ... @@ -72,17 +74,24 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
72 74 public void onAuthenticationSuccess(HttpServletRequest request,
73 75 HttpServletResponse response,
74 76 Authentication authentication) throws IOException {
75   - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  77 + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request);
  78 + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME);
  79 + String baseUrl;
  80 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  81 + baseUrl = callbackUrlScheme + ":";
  82 + } else {
  83 + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  84 + }
76 85 try {
77 86 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
78 87
79   - OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId()));
  88 + OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId()));
80 89 OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
81 90 token.getAuthorizedClientRegistrationId(),
82 91 token.getPrincipal().getName());
83   - OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType());
  92 + OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType());
84 93 SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
85   - clientRegistration);
  94 + registration);
86 95
87 96 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
88 97 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
... ... @@ -91,7 +100,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
91 100 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
92 101 } catch (Exception e) {
93 102 clearAuthenticationAttributes(request, response);
94   - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
  103 + String errorPrefix;
  104 + if (!StringUtils.isEmpty(callbackUrlScheme)) {
  105 + errorPrefix = "/?error=";
  106 + } else {
  107 + errorPrefix = "/login?loginError=";
  108 + }
  109 + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
95 110 URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
96 111 }
97 112 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth2;
  17 +
  18 +public interface TbOAuth2ParameterNames {
  19 +
  20 + String CALLBACK_URL_SCHEME = "callback_url_scheme";
  21 +
  22 +}
... ...
... ... @@ -656,7 +656,7 @@ transport:
656 656 bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
657 657 bind_port: "${LWM2M_BIND_PORT_BS:5687}"
658 658 security:
659   - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
  659 + bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}"
660 660 bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
661 661 # Only for RPK: Public & Private Key. If the keystore file is missing or not working
662 662 public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}"
... ...
... ... @@ -60,6 +60,53 @@ import java.util.concurrent.ScheduledExecutorService;
60 60 @DaoSqlTest
61 61 public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
62 62
  63 + protected final String TRANSPORT_CONFIGURATION = "{\n" +
  64 + " \"type\": \"LWM2M\",\n" +
  65 + " \"observeAttr\": {\n" +
  66 + " \"keyName\": {\n" +
  67 + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
  68 + " },\n" +
  69 + " \"observe\": [],\n" +
  70 + " \"attribute\": [\n" +
  71 + " ],\n" +
  72 + " \"telemetry\": [\n" +
  73 + " \"/3_1.0/0/9\"\n" +
  74 + " ],\n" +
  75 + " \"attributeLwm2m\": {}\n" +
  76 + " },\n" +
  77 + " \"bootstrap\": {\n" +
  78 + " \"servers\": {\n" +
  79 + " \"binding\": \"U\",\n" +
  80 + " \"shortId\": 123,\n" +
  81 + " \"lifetime\": 300,\n" +
  82 + " \"notifIfDisabled\": true,\n" +
  83 + " \"defaultMinPeriod\": 1\n" +
  84 + " },\n" +
  85 + " \"lwm2mServer\": {\n" +
  86 + " \"host\": \"localhost\",\n" +
  87 + " \"port\": 5686,\n" +
  88 + " \"serverId\": 123,\n" +
  89 + " \"serverPublicKey\": \"\",\n" +
  90 + " \"bootstrapServerIs\": false,\n" +
  91 + " \"clientHoldOffTime\": 1,\n" +
  92 + " \"bootstrapServerAccountTimeout\": 0\n" +
  93 + " },\n" +
  94 + " \"bootstrapServer\": {\n" +
  95 + " \"host\": \"localhost\",\n" +
  96 + " \"port\": 5687,\n" +
  97 + " \"serverId\": 111,\n" +
  98 + " \"securityMode\": \"NO_SEC\",\n" +
  99 + " \"serverPublicKey\": \"\",\n" +
  100 + " \"bootstrapServerIs\": true,\n" +
  101 + " \"clientHoldOffTime\": 1,\n" +
  102 + " \"bootstrapServerAccountTimeout\": 0\n" +
  103 + " }\n" +
  104 + " },\n" +
  105 + " \"clientLwM2mSettings\": {\n" +
  106 + " \"clientOnlyObserveAfterConnect\": 1\n" +
  107 + " }\n" +
  108 + "}";
  109 +
63 110 protected DeviceProfile deviceProfile;
64 111 protected ScheduledExecutorService executor;
65 112 protected TbTestWebSocketClient wsClient;
... ...
... ... @@ -22,6 +22,7 @@ import org.junit.Assert;
22 22 import org.junit.Test;
23 23 import org.thingsboard.common.util.JacksonUtil;
24 24 import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
25 26 import org.thingsboard.server.common.data.query.EntityData;
26 27 import org.thingsboard.server.common.data.query.EntityDataPageLink;
27 28 import org.thingsboard.server.common.data.query.EntityDataQuery;
... ... @@ -36,7 +37,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
36 37 import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
37 38 import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
38 39 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
39   -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
40 40
41 41 import java.util.Collections;
42 42 import java.util.List;
... ... @@ -46,60 +46,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
46 46
47 47 public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
48 48
49   - protected final String TRANSPORT_CONFIGURATION = "{\n" +
50   - " \"type\": \"LWM2M\",\n" +
51   - " \"observeAttr\": {\n" +
52   - " \"keyName\": {\n" +
53   - " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
54   - " },\n" +
55   - " \"observe\": [],\n" +
56   - " \"attribute\": [\n" +
57   - " ],\n" +
58   - " \"telemetry\": [\n" +
59   - " \"/3_1.0/0/9\"\n" +
60   - " ],\n" +
61   - " \"attributeLwm2m\": {}\n" +
62   - " },\n" +
63   - " \"bootstrap\": {\n" +
64   - " \"servers\": {\n" +
65   - " \"binding\": \"UQ\",\n" +
66   - " \"shortId\": 123,\n" +
67   - " \"lifetime\": 300,\n" +
68   - " \"notifIfDisabled\": true,\n" +
69   - " \"defaultMinPeriod\": 1\n" +
70   - " },\n" +
71   - " \"lwm2mServer\": {\n" +
72   - " \"host\": \"localhost\",\n" +
73   - " \"port\": 5685,\n" +
74   - " \"serverId\": 123,\n" +
75   - " \"securityMode\": \"NO_SEC\",\n" +
76   - " \"serverPublicKey\": \"\",\n" +
77   - " \"bootstrapServerIs\": false,\n" +
78   - " \"clientHoldOffTime\": 1,\n" +
79   - " \"bootstrapServerAccountTimeout\": 0\n" +
80   - " },\n" +
81   - " \"bootstrapServer\": {\n" +
82   - " \"host\": \"localhost\",\n" +
83   - " \"port\": 5687,\n" +
84   - " \"serverId\": 111,\n" +
85   - " \"securityMode\": \"NO_SEC\",\n" +
86   - " \"serverPublicKey\": \"\",\n" +
87   - " \"bootstrapServerIs\": true,\n" +
88   - " \"clientHoldOffTime\": 1,\n" +
89   - " \"bootstrapServerAccountTimeout\": 0\n" +
90   - " }\n" +
91   - " },\n" +
92   - " \"clientLwM2mSettings\": {\n" +
93   - " \"clientOnlyObserveAfterConnect\": 1\n" +
94   - " }\n" +
95   - "}";
96   -
97   - private final int port = 5685;
98   - private final Security security = noSec("coap://localhost:" + port, 123);
99   - private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port));
  49 + private final int PORT = 5685;
  50 + private final Security SECURITY = noSec("coap://localhost:" + PORT, 123);
  51 + private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
  52 + private final String ENDPOINT = "deviceAEndpoint";
100 53
101 54 @NotNull
102   - private Device createDevice(String deviceAEndpoint) throws Exception {
  55 + private Device createDevice() throws Exception {
103 56 Device device = new Device();
104 57 device.setName("Device A");
105 58 device.setDeviceProfileId(deviceProfile.getId());
... ... @@ -114,7 +67,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
114 67
115 68 LwM2MCredentials noSecCredentials = new LwM2MCredentials();
116 69 NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
117   - clientCredentials.setEndpoint(deviceAEndpoint);
  70 + clientCredentials.setEndpoint(ENDPOINT);
118 71 noSecCredentials.setClient(clientCredentials);
119 72 deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials));
120 73 doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
... ... @@ -125,9 +78,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
125 78 public void testConnectAndObserveTelemetry() throws Exception {
126 79 createDeviceProfile(TRANSPORT_CONFIGURATION);
127 80
128   - String deviceAEndpoint = "deviceAEndpoint";
129   -
130   - Device device = createDevice(deviceAEndpoint);
  81 + Device device = createDevice();
131 82
132 83 SingleEntityFilter sef = new SingleEntityFilter();
133 84 sef.setSingleEntity(device.getId());
... ... @@ -144,8 +95,8 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
144 95 wsClient.waitForReply();
145 96
146 97 wsClient.registerWaitForUpdate();
147   - LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint);
148   - client.init(security, coapConfig);
  98 + LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT);
  99 + client.init(SECURITY, COAP_CONFIG);
149 100 String msg = wsClient.waitForUpdate();
150 101
151 102 EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
... ...
... ... @@ -47,54 +47,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
47 47
48 48 public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
49 49
50   - protected final String TRANSPORT_CONFIGURATION = "{\n" +
51   - " \"type\": \"LWM2M\",\n" +
52   - " \"observeAttr\": {\n" +
53   - " \"keyName\": {\n" +
54   - " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
55   - " },\n" +
56   - " \"observe\": [],\n" +
57   - " \"attribute\": [\n" +
58   - " ],\n" +
59   - " \"telemetry\": [\n" +
60   - " \"/3_1.0/0/9\"\n" +
61   - " ],\n" +
62   - " \"attributeLwm2m\": {}\n" +
63   - " },\n" +
64   - " \"bootstrap\": {\n" +
65   - " \"servers\": {\n" +
66   - " \"binding\": \"UQ\",\n" +
67   - " \"shortId\": 123,\n" +
68   - " \"lifetime\": 300,\n" +
69   - " \"notifIfDisabled\": true,\n" +
70   - " \"defaultMinPeriod\": 1\n" +
71   - " },\n" +
72   - " \"lwm2mServer\": {\n" +
73   - " \"host\": \"localhost\",\n" +
74   - " \"port\": 5686,\n" +
75   - " \"serverId\": 123,\n" +
76   - " \"serverPublicKey\": \"\",\n" +
77   - " \"bootstrapServerIs\": false,\n" +
78   - " \"clientHoldOffTime\": 1,\n" +
79   - " \"bootstrapServerAccountTimeout\": 0\n" +
80   - " },\n" +
81   - " \"bootstrapServer\": {\n" +
82   - " \"host\": \"localhost\",\n" +
83   - " \"port\": 5687,\n" +
84   - " \"serverId\": 111,\n" +
85   - " \"securityMode\": \"NO_SEC\",\n" +
86   - " \"serverPublicKey\": \"\",\n" +
87   - " \"bootstrapServerIs\": true,\n" +
88   - " \"clientHoldOffTime\": 1,\n" +
89   - " \"bootstrapServerAccountTimeout\": 0\n" +
90   - " }\n" +
91   - " },\n" +
92   - " \"clientLwM2mSettings\": {\n" +
93   - " \"clientOnlyObserveAfterConnect\": 1\n" +
94   - " }\n" +
95   - "}";
96   -
97   -
98 50 private final int port = 5686;
99 51 private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port));
100 52 private final String endpoint = "deviceAEndpoint";
... ...
... ... @@ -17,40 +17,37 @@ package org.thingsboard.server.transport.lwm2m.client;
17 17
18 18 import lombok.Data;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.network.CoapEndpoint;
20 21 import org.eclipse.californium.core.network.config.NetworkConfig;
21   -import org.eclipse.californium.elements.Connector;
  22 +import org.eclipse.californium.core.observe.ObservationStore;
22 23 import org.eclipse.californium.scandium.DTLSConnector;
23 24 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
24   -import org.eclipse.californium.scandium.dtls.ClientHandshaker;
25   -import org.eclipse.californium.scandium.dtls.DTLSSession;
26   -import org.eclipse.californium.scandium.dtls.HandshakeException;
27   -import org.eclipse.californium.scandium.dtls.Handshaker;
28   -import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker;
29   -import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker;
30   -import org.eclipse.californium.scandium.dtls.ServerHandshaker;
31   -import org.eclipse.californium.scandium.dtls.SessionAdapter;
32 25 import org.eclipse.leshan.client.californium.LeshanClient;
33 26 import org.eclipse.leshan.client.californium.LeshanClientBuilder;
34 27 import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
35 28 import org.eclipse.leshan.client.object.Security;
36 29 import org.eclipse.leshan.client.object.Server;
37 30 import org.eclipse.leshan.client.observer.LwM2mClientObserver;
  31 +import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
38 32 import org.eclipse.leshan.client.resource.ObjectsInitializer;
39 33 import org.eclipse.leshan.client.servers.ServerIdentity;
  34 +import org.eclipse.leshan.core.LwM2mId;
40 35 import org.eclipse.leshan.core.ResponseCode;
41   -import org.eclipse.leshan.core.californium.DefaultEndpointFactory;
  36 +import org.eclipse.leshan.core.californium.EndpointFactory;
  37 +import org.eclipse.leshan.core.model.InvalidDDFFileException;
42 38 import org.eclipse.leshan.core.model.LwM2mModel;
43 39 import org.eclipse.leshan.core.model.ObjectLoader;
44 40 import org.eclipse.leshan.core.model.ObjectModel;
45 41 import org.eclipse.leshan.core.model.StaticModel;
46 42 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
47 43 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
48   -import org.eclipse.leshan.core.request.BindingMode;
49 44 import org.eclipse.leshan.core.request.BootstrapRequest;
50 45 import org.eclipse.leshan.core.request.DeregisterRequest;
51 46 import org.eclipse.leshan.core.request.RegisterRequest;
52 47 import org.eclipse.leshan.core.request.UpdateRequest;
53 48
  49 +import java.io.IOException;
  50 +import java.net.InetSocketAddress;
54 51 import java.util.ArrayList;
55 52 import java.util.List;
56 53 import java.util.concurrent.ScheduledExecutorService;
... ... @@ -67,7 +64,7 @@ public class LwM2MTestClient {
67 64 private final String endpoint;
68 65 private LeshanClient client;
69 66
70   - public void init(Security security, NetworkConfig coapConfig) {
  67 + public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException {
71 68 String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"};
72 69 List<ObjectModel> models = new ArrayList<>();
73 70 for (String resourceName : resources) {
... ... @@ -76,82 +73,51 @@ public class LwM2MTestClient {
76 73 LwM2mModel model = new StaticModel(models);
77 74 ObjectsInitializer initializer = new ObjectsInitializer(model);
78 75 initializer.setInstancesForObject(SECURITY, security);
79   - initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false));
  76 + initializer.setInstancesForObject(SERVER, new Server(123, 300));
80 77 initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
  78 + initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class);
81 79
82 80 DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
83 81 dtlsConfig.setRecommendedCipherSuitesOnly(true);
  82 + dtlsConfig.setClientOnly();
84 83
85 84 DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
86 85 engineFactory.setReconnectOnUpdate(false);
87 86 engineFactory.setResumeOnConnect(true);
88 87
89   - DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) {
  88 + EndpointFactory endpointFactory = new EndpointFactory() {
  89 +
90 90 @Override
91   - protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
92   -
93   - return new DTLSConnector(dtlsConfig) {
94   - @Override
95   - protected void onInitializeHandshaker(Handshaker handshaker) {
96   - handshaker.addSessionListener(new SessionAdapter() {
97   -
98   - @Override
99   - public void handshakeStarted(Handshaker handshaker) throws HandshakeException {
100   - if (handshaker instanceof ServerHandshaker) {
101   - log.info("DTLS Full Handshake initiated by server : STARTED ...");
102   - } else if (handshaker instanceof ResumingServerHandshaker) {
103   - log.info("DTLS abbreviated Handshake initiated by server : STARTED ...");
104   - } else if (handshaker instanceof ClientHandshaker) {
105   - log.info("DTLS Full Handshake initiated by client : STARTED ...");
106   - } else if (handshaker instanceof ResumingClientHandshaker) {
107   - log.info("DTLS abbreviated Handshake initiated by client : STARTED ...");
108   - }
109   - }
110   -
111   - @Override
112   - public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession)
113   - throws HandshakeException {
114   - if (handshaker instanceof ServerHandshaker) {
115   - log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
116   - } else if (handshaker instanceof ResumingServerHandshaker) {
117   - log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
118   - } else if (handshaker instanceof ClientHandshaker) {
119   - log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
120   - } else if (handshaker instanceof ResumingClientHandshaker) {
121   - log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
122   - }
123   - }
124   -
125   - @Override
126   - public void handshakeFailed(Handshaker handshaker, Throwable error) {
127   - /** get cause */
128   - String cause;
129   - if (error != null) {
130   - if (error.getMessage() != null) {
131   - cause = error.getMessage();
132   - } else {
133   - cause = error.getClass().getName();
134   - }
135   - } else {
136   - cause = "unknown cause";
137   - }
138   -
139   - if (handshaker instanceof ServerHandshaker) {
140   - log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause);
141   - } else if (handshaker instanceof ResumingServerHandshaker) {
142   - log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause);
143   - } else if (handshaker instanceof ClientHandshaker) {
144   - log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause);
145   - } else if (handshaker instanceof ResumingClientHandshaker) {
146   - log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause);
147   - }
148   - }
149   - });
150   - }
151   - };
  91 + public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig,
  92 + ObservationStore store) {
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setInetSocketAddress(address);
  95 + builder.setNetworkConfig(coapConfig);
  96 + return builder.build();
  97 + }
  98 +
  99 + @Override
  100 + public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig,
  101 + ObservationStore store) {
  102 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  103 + DtlsConnectorConfig.Builder dtlsConfigBuilder = new DtlsConnectorConfig.Builder(dtlsConfig);
  104 +
  105 + // tricks to be able to change psk information on the fly
  106 +// AdvancedPskStore pskStore = dtlsConfig.getAdvancedPskStore();
  107 +// if (pskStore != null) {
  108 +// PskPublicInformation identity = pskStore.getIdentity(null, null);
  109 +// SecretKey key = pskStore
  110 +// .requestPskSecretResult(ConnectionId.EMPTY, null, identity, null, null, null).getSecret();
  111 +// singlePSKStore = new SinglePSKStore(identity, key);
  112 +// dtlsConfigBuilder.setAdvancedPskStore(singlePSKStore);
  113 +// }
  114 + builder.setConnector(new DTLSConnector(dtlsConfigBuilder.build()));
  115 + builder.setNetworkConfig(coapConfig);
  116 + return builder.build();
152 117 }
153 118 };
154 119
  120 +
155 121 LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
156 122 builder.setLocalAddress("0.0.0.0", 11000);
157 123 builder.setObjects(initializer.createAll());
... ... @@ -246,6 +212,11 @@ public class LwM2MTestClient {
246 212 public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
247 213 log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
248 214 }
  215 +
  216 + @Override
  217 + public void onUnexpectedError(Throwable unexpectedError) {
  218 +
  219 + }
249 220 };
250 221 this.client.addObserver(observer);
251 222
... ...
... ... @@ -97,7 +97,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
97 97 }
98 98
99 99 @Override
100   - public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) {
  100 + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) {
101 101 log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
102 102
103 103 switch (resourceid) {
... ... @@ -112,7 +112,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
112 112 fireResourcesChange(resourceid);
113 113 return WriteResponse.success();
114 114 default:
115   - return super.write(identity, resourceid, value);
  115 + return super.write(identity, replace, resourceid, value);
116 116 }
117 117 }
118 118
... ...
... ... @@ -16,20 +16,31 @@
16 16 package org.thingsboard.server.dao.oauth2;
17 17
18 18 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
19   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
20   -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
  19 +import org.thingsboard.server.common.data.oauth2.OAuth2Info;
  20 +import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
  21 +import org.thingsboard.server.common.data.oauth2.PlatformType;
  22 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
  23 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
21 24
22 25 import java.util.List;
23 26 import java.util.UUID;
24 27
25 28 public interface OAuth2Service {
26   - List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName);
  29 + List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
27 30
  31 + @Deprecated
28 32 void saveOAuth2Params(OAuth2ClientsParams oauth2Params);
29 33
  34 + @Deprecated
30 35 OAuth2ClientsParams findOAuth2Params();
31 36
32   - OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id);
  37 + void saveOAuth2Info(OAuth2Info oauth2Info);
33 38
34   - List<OAuth2ClientRegistrationInfo> findAllClientRegistrationInfos();
  39 + OAuth2Info findOAuth2Info();
  40 +
  41 + OAuth2Registration findRegistration(UUID id);
  42 +
  43 + List<OAuth2Registration> findAllRegistrations();
  44 +
  45 + String findCallbackUrlScheme(UUID registrationId, String pkgName);
35 46 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2DomainId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2DomainId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2DomainId fromString(String oauth2DomainId) {
  31 + return new OAuth2DomainId(UUID.fromString(oauth2DomainId));
  32 + }
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2MobileId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2MobileId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2MobileId fromString(String oauth2MobileId) {
  31 + return new OAuth2MobileId(UUID.fromString(oauth2MobileId));
  32 + }
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2ParamsId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2ParamsId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2ParamsId fromString(String oauth2ParamsId) {
  31 + return new OAuth2ParamsId(UUID.fromString(oauth2ParamsId));
  32 + }
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public class OAuth2RegistrationId extends UUIDBased {
  24 +
  25 + @JsonCreator
  26 + public OAuth2RegistrationId(@JsonProperty("id") UUID id) {
  27 + super(id);
  28 + }
  29 +
  30 + public static OAuth2RegistrationId fromString(String oauth2RegistrationId) {
  31 + return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId));
  32 + }
  33 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationId.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java
... ... @@ -13,13 +13,15 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.id;
  16 +package org.thingsboard.server.common.data.id.deprecated;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import org.thingsboard.server.common.data.id.UUIDBased;
20 21
21 22 import java.util.UUID;
22 23
  24 +@Deprecated
23 25 public class OAuth2ClientRegistrationId extends UUIDBased {
24 26
25 27 @JsonCreator
... ...
common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationInfoId.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java
... ... @@ -13,13 +13,15 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.id;
  16 +package org.thingsboard.server.common.data.id.deprecated;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import org.thingsboard.server.common.data.id.UUIDBased;
20 21
21 22 import java.util.UUID;
22 23
  24 +@Deprecated
23 25 public class OAuth2ClientRegistrationInfoId extends UUIDBased {
24 26
25 27 @JsonCreator
... ...
... ... @@ -20,10 +20,8 @@ import lombok.EqualsAndHashCode;
20 20 import lombok.NoArgsConstructor;
21 21 import lombok.ToString;
22 22 import org.thingsboard.server.common.data.HasName;
23   -import org.thingsboard.server.common.data.HasTenantId;
24 23 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 24 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
26   -import org.thingsboard.server.common.data.id.TenantId;
27 25
28 26 import java.util.List;
29 27
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2DomainId;
  24 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Domain extends BaseData<OAuth2DomainId> {
  31 +
  32 + private OAuth2ParamsId oauth2ParamsId;
  33 + private String domainName;
  34 + private SchemeType domainScheme;
  35 +
  36 + public OAuth2Domain(OAuth2Domain domain) {
  37 + super(domain);
  38 + this.oauth2ParamsId = domain.oauth2ParamsId;
  39 + this.domainName = domain.domainName;
  40 + this.domainScheme = domain.domainScheme;
  41 + }
  42 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class OAuth2DomainInfo {
  32 + private SchemeType scheme;
  33 + private String name;
  34 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.*;
  19 +
  20 +import java.util.List;
  21 +
  22 +@EqualsAndHashCode
  23 +@Data
  24 +@ToString
  25 +@Builder(toBuilder = true)
  26 +@NoArgsConstructor
  27 +@AllArgsConstructor
  28 +public class OAuth2Info {
  29 + private boolean enabled;
  30 + private List<OAuth2ParamsInfo> oauth2ParamsInfos;
  31 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2MobileId;
  24 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Mobile extends BaseData<OAuth2MobileId> {
  31 +
  32 + private OAuth2ParamsId oauth2ParamsId;
  33 + private String pkgName;
  34 + private String callbackUrlScheme;
  35 +
  36 + public OAuth2Mobile(OAuth2Mobile mobile) {
  37 + super(mobile);
  38 + this.oauth2ParamsId = mobile.oauth2ParamsId;
  39 + this.pkgName = mobile.pkgName;
  40 + this.callbackUrlScheme = mobile.callbackUrlScheme;
  41 + }
  42 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class OAuth2MobileInfo {
  32 + private String pkgName;
  33 + private String callbackUrlScheme;
  34 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.BaseData;
  23 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Data
  28 +@ToString
  29 +@NoArgsConstructor
  30 +public class OAuth2Params extends BaseData<OAuth2ParamsId> {
  31 +
  32 + private boolean enabled;
  33 + private TenantId tenantId;
  34 +
  35 + public OAuth2Params(OAuth2Params oauth2Params) {
  36 + super(oauth2Params);
  37 + this.enabled = oauth2Params.enabled;
  38 + this.tenantId = oauth2Params.tenantId;
  39 + }
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import lombok.NoArgsConstructor;
  23 +import lombok.ToString;
  24 +
  25 +import java.util.List;
  26 +
  27 +@EqualsAndHashCode
  28 +@Data
  29 +@ToString
  30 +@Builder(toBuilder = true)
  31 +@NoArgsConstructor
  32 +@AllArgsConstructor
  33 +public class OAuth2ParamsInfo {
  34 +
  35 + private List<OAuth2DomainInfo> domainInfos;
  36 + private List<OAuth2MobileInfo> mobileInfos;
  37 + private List<OAuth2RegistrationInfo> clientRegistrations;
  38 +
  39 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.NoArgsConstructor;
  22 +import lombok.ToString;
  23 +import org.thingsboard.server.common.data.HasName;
  24 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  25 +import org.thingsboard.server.common.data.id.OAuth2ParamsId;
  26 +import org.thingsboard.server.common.data.id.OAuth2RegistrationId;
  27 +
  28 +import java.util.List;
  29 +
  30 +@EqualsAndHashCode(callSuper = true)
  31 +@Data
  32 +@ToString(exclude = {"clientSecret"})
  33 +@NoArgsConstructor
  34 +public class OAuth2Registration extends SearchTextBasedWithAdditionalInfo<OAuth2RegistrationId> implements HasName {
  35 +
  36 + private OAuth2ParamsId oauth2ParamsId;
  37 + private OAuth2MapperConfig mapperConfig;
  38 + private String clientId;
  39 + private String clientSecret;
  40 + private String authorizationUri;
  41 + private String accessTokenUri;
  42 + private List<String> scope;
  43 + private String userInfoUri;
  44 + private String userNameAttributeName;
  45 + private String jwkSetUri;
  46 + private String clientAuthenticationMethod;
  47 + private String loginButtonLabel;
  48 + private String loginButtonIcon;
  49 + private List<PlatformType> platforms;
  50 +
  51 + public OAuth2Registration(OAuth2Registration registration) {
  52 + super(registration);
  53 + this.oauth2ParamsId = registration.oauth2ParamsId;
  54 + this.mapperConfig = registration.mapperConfig;
  55 + this.clientId = registration.clientId;
  56 + this.clientSecret = registration.clientSecret;
  57 + this.authorizationUri = registration.authorizationUri;
  58 + this.accessTokenUri = registration.accessTokenUri;
  59 + this.scope = registration.scope;
  60 + this.userInfoUri = registration.userInfoUri;
  61 + this.userNameAttributeName = registration.userNameAttributeName;
  62 + this.jwkSetUri = registration.jwkSetUri;
  63 + this.clientAuthenticationMethod = registration.clientAuthenticationMethod;
  64 + this.loginButtonLabel = registration.loginButtonLabel;
  65 + this.loginButtonIcon = registration.loginButtonIcon;
  66 + this.platforms = registration.platforms;
  67 + }
  68 +
  69 + @Override
  70 + @JsonProperty(access = JsonProperty.Access.READ_ONLY)
  71 + public String getName() {
  72 + return loginButtonLabel;
  73 + }
  74 +
  75 + @Override
  76 + public String getSearchText() {
  77 + return getName();
  78 + }
  79 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java
... ... @@ -17,7 +17,6 @@ package org.thingsboard.server.common.data.oauth2;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.*;
20   -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
21 20
22 21 import java.util.List;
23 22
... ... @@ -27,7 +26,7 @@ import java.util.List;
27 26 @NoArgsConstructor
28 27 @AllArgsConstructor
29 28 @Builder
30   -public class ClientRegistrationDto {
  29 +public class OAuth2RegistrationInfo {
31 30 private OAuth2MapperConfig mapperConfig;
32 31 private String clientId;
33 32 private String clientSecret;
... ... @@ -40,5 +39,6 @@ public class ClientRegistrationDto {
40 39 private String clientAuthenticationMethod;
41 40 private String loginButtonLabel;
42 41 private String loginButtonIcon;
  42 + private List<PlatformType> platforms;
43 43 private JsonNode additionalInfo;
44 44 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2;
  17 +
  18 +public enum PlatformType {
  19 + WEB, ANDROID, IOS
  20 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.*;
  20 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
  21 +
  22 +import java.util.List;
  23 +
  24 +@Deprecated
  25 +@EqualsAndHashCode
  26 +@Data
  27 +@ToString(exclude = {"clientSecret"})
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +@Builder
  31 +public class ClientRegistrationDto {
  32 + private OAuth2MapperConfig mapperConfig;
  33 + private String clientId;
  34 + private String clientSecret;
  35 + private String authorizationUri;
  36 + private String accessTokenUri;
  37 + private List<String> scope;
  38 + private String userInfoUri;
  39 + private String userNameAttributeName;
  40 + private String jwkSetUri;
  41 + private String clientAuthenticationMethod;
  42 + private String loginButtonLabel;
  43 + private String loginButtonIcon;
  44 + private JsonNode additionalInfo;
  45 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/DomainInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java
... ... @@ -13,10 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import lombok.*;
  19 +import org.thingsboard.server.common.data.oauth2.SchemeType;
19 20
  21 +@Deprecated
20 22 @EqualsAndHashCode
21 23 @Data
22 24 @ToString
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ExtendedOAuth2ClientRegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java
... ... @@ -13,11 +13,14 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import lombok.Data;
19 19 import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.oauth2.SchemeType;
  21 +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
20 22
  23 +@Deprecated
21 24 @EqualsAndHashCode(callSuper = true)
22 25 @Data
23 26 public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo {
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistration.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java
... ... @@ -13,16 +13,18 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import lombok.Data;
19 19 import lombok.EqualsAndHashCode;
20 20 import lombok.NoArgsConstructor;
21 21 import lombok.ToString;
22 22 import org.thingsboard.server.common.data.BaseData;
23   -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId;
24   -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  23 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationId;
  24 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId;
  25 +import org.thingsboard.server.common.data.oauth2.SchemeType;
25 26
  27 +@Deprecated
26 28 @EqualsAndHashCode(callSuper = true)
27 29 @Data
28 30 @ToString
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistrationInfo.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonProperty;
19 19 import lombok.Data;
... ... @@ -22,10 +22,12 @@ import lombok.NoArgsConstructor;
22 22 import lombok.ToString;
23 23 import org.thingsboard.server.common.data.HasName;
24 24 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25   -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId;
  25 +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId;
  26 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
26 27
27 28 import java.util.List;
28 29
  30 +@Deprecated
29 31 @EqualsAndHashCode(callSuper = true)
30 32 @Data
31 33 @ToString(exclude = {"clientSecret"})
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsDomainParams.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java
... ... @@ -13,13 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import lombok.*;
19 19
20 20 import java.util.List;
21   -import java.util.Set;
22 21
  22 +@Deprecated
23 23 @EqualsAndHashCode
24 24 @Data
25 25 @ToString
... ...
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsParams.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java
... ... @@ -13,13 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.common.data.oauth2;
  16 +package org.thingsboard.server.common.data.oauth2.deprecated;
17 17
18 18 import lombok.*;
19 19
20 20 import java.util.List;
21   -import java.util.Set;
22 21
  22 +@Deprecated
23 23 @EqualsAndHashCode
24 24 @Data
25 25 @ToString
... ...
... ... @@ -310,10 +310,12 @@ message SessionCloseNotificationProto {
310 310
311 311 message SubscribeToAttributeUpdatesMsg {
312 312 bool unsubscribe = 1;
  313 + SessionType sessionType = 2;
313 314 }
314 315
315 316 message SubscribeToRPCMsg {
316 317 bool unsubscribe = 1;
  318 + SessionType sessionType = 2;
317 319 }
318 320
319 321 message ToDeviceRpcRequestMsg {
... ...
... ... @@ -483,13 +483,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
483 483 String title = exchange.getQueryParameter("title");
484 484 String version = exchange.getQueryParameter("version");
485 485 if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
  486 + String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();
486 487 if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) {
487   - String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();
488 488 String strChunkSize = exchange.getQueryParameter("size");
489 489 String strChunk = exchange.getQueryParameter("chunk");
490 490 int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
491 491 int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk);
492 492 exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk));
  493 + }
  494 + else if (firmwareId != null) {
  495 + sendOtaData(exchange, firmwareId);
493 496 } else {
494 497 exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
495 498 }
... ... @@ -505,6 +508,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
505 508 }
506 509 }
507 510
  511 + private void sendOtaData(CoapExchange exchange, String firmwareId) {
  512 + Response response = new Response(CoAP.ResponseCode.CONTENT);
  513 + byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId);
  514 + if (fwData != null && fwData.length > 0) {
  515 + response.setPayload(fwData);
  516 + if (exchange.getRequestOptions().getBlock2() != null) {
  517 + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
  518 + boolean moreFlag = fwData.length > chunkSize;
  519 + response.getOptions().setBlock2(chunkSize, moreFlag, 0);
  520 + }
  521 + exchange.respond(response);
  522 + }
  523 + }
  524 +
508 525 private static class CoapSessionListener implements SessionMsgListener {
509 526
510 527 private final CoapTransportResource coapTransportResource;
... ... @@ -572,7 +589,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
572 589 }
573 590 if (msg.getPersisted()) {
574 591 RpcStatus status;
575   - if (successful) {
  592 + if (!successful) {
576 593 status = RpcStatus.FAILED;
577 594 } else if (msg.getOneway()) {
578 595 status = RpcStatus.SUCCESSFUL;
... ...
... ... @@ -102,15 +102,15 @@ public class LwM2MTransportBootstrapService {
102 102 builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort());
103 103
104 104 /** Create CoAP Config */
105   - builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort()));
  105 + builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
106 106
107 107 /** Define model provider (Create Models )*/
108 108
109 109 /** Create credentials */
110 110 this.setServerWithCredentials(builder);
111 111
112   - /** Set securityStore with new ConfigStore */
113   - builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
  112 +// /** Set securityStore with new ConfigStore */
  113 +// builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
114 114
115 115 /** SecurityStore */
116 116 builder.setSecurityStore(lwM2MBootstrapSecurityStore);
... ...
... ... @@ -69,7 +69,7 @@ public class LwM2MBootstrapConfig {
69 69 server0.lifetime = servers.getLifetime();
70 70 server0.defaultMinPeriod = servers.getDefaultMinPeriod();
71 71 server0.notifIfDisabled = servers.isNotifIfDisabled();
72   - server0.binding = BindingMode.valueOf(servers.getBinding());
  72 + server0.binding = BindingMode.parse(servers.getBinding());
73 73 configBs.servers.put(0, server0);
74 74 /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
75 75 this.bootstrapServer.setBootstrapServerIs(true);
... ...
... ... @@ -30,7 +30,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
30 30 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
31 31 import org.springframework.stereotype.Service;
32 32 import org.thingsboard.server.gen.transport.TransportProtos;
33   -import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo;
  33 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
34 34 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
35 35 import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
36 36 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
... ... @@ -40,7 +40,7 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
40 40 import java.io.IOException;
41 41 import java.security.GeneralSecurityException;
42 42 import java.util.Collections;
43   -import java.util.List;
  43 +import java.util.Iterator;
44 44 import java.util.UUID;
45 45
46 46 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER;
... ... @@ -71,8 +71,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
71 71 }
72 72
73 73 @Override
74   - public List<SecurityInfo> getAllByEndpoint(String endPoint) {
75   - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
  74 + public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
  75 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
76 76 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
77 77 /* add value to store from BootstrapJson */
78 78 this.setBootstrapConfigScurityInfo(store);
... ... @@ -88,7 +88,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
88 88 } catch (InvalidConfigurationException e) {
89 89 log.error("", e);
90 90 }
91   - return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo());
  91 + return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
92 92 }
93 93 }
94 94 return null;
... ... @@ -96,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
96 96
97 97 @Override
98 98 public SecurityInfo getByIdentity(String identity) {
99   - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
  99 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
100 100 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
101 101 /* add value to store from BootstrapJson */
102 102 this.setBootstrapConfigScurityInfo(store);
... ... @@ -113,7 +113,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
113 113 return null;
114 114 }
115 115
116   - private void setBootstrapConfigScurityInfo(EndpointSecurityInfo store) {
  116 + private void setBootstrapConfigScurityInfo(TbLwM2MSecurityInfo store) {
117 117 /* BootstrapConfig */
118 118 LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
119 119 if (lwM2MBootstrapConfig != null) {
... ... @@ -150,7 +150,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
150 150 }
151 151 }
152 152
153   - private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) {
  153 + private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) {
154 154 try {
155 155 LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig();
156 156 if (lwM2MBootstrapConfig != null) {
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.core.request.BootstrapRequest;
19 20 import org.eclipse.leshan.core.request.Identity;
20 21 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
21 22 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
... ... @@ -25,7 +26,7 @@ import org.eclipse.leshan.server.security.SecurityChecker;
25 26 import org.eclipse.leshan.server.security.SecurityInfo;
26 27
27 28 import java.util.Collections;
28   -import java.util.List;
  29 +import java.util.Iterator;
29 30
30 31 @Slf4j
31 32 public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
... ... @@ -50,16 +51,17 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
50 51 }
51 52
52 53 @SuppressWarnings("deprecation")
53   - public BootstrapSession begin(String endpoint, Identity clientIdentity) {
  54 + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
54 55 boolean authorized;
55 56 if (bsSecurityStore != null) {
56   - List<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint);
  57 + Iterator<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ?
  58 + Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName());
57 59 log.info("Bootstrap session started securityInfos: [{}]", securityInfos);
58   - authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos);
  60 + authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
59 61 } else {
60 62 authorized = true;
61 63 }
62   - DefaultBootstrapSession session = new DefaultBootstrapSession(endpoint, clientIdentity, authorized);
  64 + DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
63 65 log.info("Bootstrap session started : {}", session);
64 66 return session;
65 67 }
... ...