Commit da082e27005dd4d8ab35984b46d12dc56c4f6b64
Merge remote-tracking branch 'upstream/master' into edge-3.3
Showing
41 changed files
with
3426 additions
and
456 deletions
Too many changes to show.
To preserve performance only 41 of 433 files are displayed.
@@ -223,6 +223,27 @@ | @@ -223,6 +223,27 @@ | ||
223 | "funcBody": null, | 223 | "funcBody": null, |
224 | "usePostProcessing": null, | 224 | "usePostProcessing": null, |
225 | "postFuncBody": null | 225 | "postFuncBody": null |
226 | + }, | ||
227 | + { | ||
228 | + "name": "fw_url", | ||
229 | + "type": "attribute", | ||
230 | + "label": "fw_url", | ||
231 | + "color": "#e91e63", | ||
232 | + "settings": { | ||
233 | + "columnWidth": "0px", | ||
234 | + "useCellStyleFunction": false, | ||
235 | + "cellStyleFunction": "", | ||
236 | + "useCellContentFunction": false, | ||
237 | + "cellContentFunction": "", | ||
238 | + "defaultColumnVisibility": "hidden", | ||
239 | + "columnSelectionToDisplay": "disabled" | ||
240 | + }, | ||
241 | + "_hash": 0.4204673738685043, | ||
242 | + "units": null, | ||
243 | + "decimals": null, | ||
244 | + "funcBody": null, | ||
245 | + "usePostProcessing": null, | ||
246 | + "postFuncBody": null | ||
226 | } | 247 | } |
227 | ] | 248 | ] |
228 | } | 249 | } |
@@ -249,23 +270,23 @@ | @@ -249,23 +270,23 @@ | ||
249 | "icon": "edit", | 270 | "icon": "edit", |
250 | "type": "customPretty", | 271 | "type": "customPretty", |
251 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | 272 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", |
252 | - "customCss": "", | 273 | + "customCss": "form {\n min-width: 300px !important;\n}", |
253 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | 274 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", |
254 | "customResources": [], | 275 | "customResources": [], |
255 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | 276 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" |
256 | }, | 277 | }, |
257 | { | 278 | { |
258 | - "name": "Download firware", | 279 | + "name": "Download firmware", |
259 | "icon": "file_download", | 280 | "icon": "file_download", |
260 | "type": "custom", | 281 | "type": "custom", |
261 | - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", | 282 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
262 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" | 283 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" |
263 | }, | 284 | }, |
264 | { | 285 | { |
265 | - "name": "Copy checksum", | 286 | + "name": "Copy checksum/URL", |
266 | "icon": "content_copy", | 287 | "icon": "content_copy", |
267 | "type": "custom", | 288 | "type": "custom", |
268 | - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", | 289 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
269 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | 290 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" |
270 | } | 291 | } |
271 | ] | 292 | ] |
@@ -997,6 +1018,27 @@ | @@ -997,6 +1018,27 @@ | ||
997 | "funcBody": null, | 1018 | "funcBody": null, |
998 | "usePostProcessing": null, | 1019 | "usePostProcessing": null, |
999 | "postFuncBody": null | 1020 | "postFuncBody": null |
1021 | + }, | ||
1022 | + { | ||
1023 | + "name": "fw_url", | ||
1024 | + "type": "attribute", | ||
1025 | + "label": "fw_url", | ||
1026 | + "color": "#e91e63", | ||
1027 | + "settings": { | ||
1028 | + "columnWidth": "0px", | ||
1029 | + "useCellStyleFunction": false, | ||
1030 | + "cellStyleFunction": "", | ||
1031 | + "useCellContentFunction": false, | ||
1032 | + "cellContentFunction": "", | ||
1033 | + "defaultColumnVisibility": "hidden", | ||
1034 | + "columnSelectionToDisplay": "disabled" | ||
1035 | + }, | ||
1036 | + "_hash": 0.4204673738685043, | ||
1037 | + "units": null, | ||
1038 | + "decimals": null, | ||
1039 | + "funcBody": null, | ||
1040 | + "usePostProcessing": null, | ||
1041 | + "postFuncBody": null | ||
1000 | } | 1042 | } |
1001 | ] | 1043 | ] |
1002 | } | 1044 | } |
@@ -1023,23 +1065,23 @@ | @@ -1023,23 +1065,23 @@ | ||
1023 | "icon": "edit", | 1065 | "icon": "edit", |
1024 | "type": "customPretty", | 1066 | "type": "customPretty", |
1025 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | 1067 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", |
1026 | - "customCss": "", | 1068 | + "customCss": "form {\n min-width: 300px !important;\n}", |
1027 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | 1069 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", |
1028 | "customResources": [], | 1070 | "customResources": [], |
1029 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | 1071 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" |
1030 | }, | 1072 | }, |
1031 | { | 1073 | { |
1032 | - "name": "Download firware", | 1074 | + "name": "Download firmware", |
1033 | "icon": "file_download", | 1075 | "icon": "file_download", |
1034 | "type": "custom", | 1076 | "type": "custom", |
1035 | - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", | 1077 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1036 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" | 1078 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" |
1037 | }, | 1079 | }, |
1038 | { | 1080 | { |
1039 | - "name": "Copy checksum", | 1081 | + "name": "Copy checksum/URL", |
1040 | "icon": "content_copy", | 1082 | "icon": "content_copy", |
1041 | "type": "custom", | 1083 | "type": "custom", |
1042 | - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", | 1084 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1043 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | 1085 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" |
1044 | } | 1086 | } |
1045 | ] | 1087 | ] |
@@ -1273,6 +1315,27 @@ | @@ -1273,6 +1315,27 @@ | ||
1273 | "funcBody": null, | 1315 | "funcBody": null, |
1274 | "usePostProcessing": null, | 1316 | "usePostProcessing": null, |
1275 | "postFuncBody": null | 1317 | "postFuncBody": null |
1318 | + }, | ||
1319 | + { | ||
1320 | + "name": "fw_url", | ||
1321 | + "type": "attribute", | ||
1322 | + "label": "fw_url", | ||
1323 | + "color": "#e91e63", | ||
1324 | + "settings": { | ||
1325 | + "columnWidth": "0px", | ||
1326 | + "useCellStyleFunction": false, | ||
1327 | + "cellStyleFunction": "", | ||
1328 | + "useCellContentFunction": false, | ||
1329 | + "cellContentFunction": "", | ||
1330 | + "defaultColumnVisibility": "hidden", | ||
1331 | + "columnSelectionToDisplay": "disabled" | ||
1332 | + }, | ||
1333 | + "_hash": 0.4204673738685043, | ||
1334 | + "units": null, | ||
1335 | + "decimals": null, | ||
1336 | + "funcBody": null, | ||
1337 | + "usePostProcessing": null, | ||
1338 | + "postFuncBody": null | ||
1276 | } | 1339 | } |
1277 | ] | 1340 | ] |
1278 | } | 1341 | } |
@@ -1299,23 +1362,23 @@ | @@ -1299,23 +1362,23 @@ | ||
1299 | "icon": "edit", | 1362 | "icon": "edit", |
1300 | "type": "customPretty", | 1363 | "type": "customPretty", |
1301 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | 1364 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", |
1302 | - "customCss": "", | 1365 | + "customCss": "form {\n min-width: 300px !important;\n}", |
1303 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | 1366 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", |
1304 | "customResources": [], | 1367 | "customResources": [], |
1305 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | 1368 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" |
1306 | }, | 1369 | }, |
1307 | { | 1370 | { |
1308 | - "name": "Download firware", | 1371 | + "name": "Download firmware", |
1309 | "icon": "file_download", | 1372 | "icon": "file_download", |
1310 | "type": "custom", | 1373 | "type": "custom", |
1311 | - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", | 1374 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1312 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" | 1375 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" |
1313 | }, | 1376 | }, |
1314 | { | 1377 | { |
1315 | - "name": "Copy checksum", | 1378 | + "name": "Copy checksum/URL", |
1316 | "icon": "content_copy", | 1379 | "icon": "content_copy", |
1317 | "type": "custom", | 1380 | "type": "custom", |
1318 | - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", | 1381 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1319 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | 1382 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" |
1320 | } | 1383 | } |
1321 | ] | 1384 | ] |
@@ -1549,6 +1612,27 @@ | @@ -1549,6 +1612,27 @@ | ||
1549 | "funcBody": null, | 1612 | "funcBody": null, |
1550 | "usePostProcessing": null, | 1613 | "usePostProcessing": null, |
1551 | "postFuncBody": null | 1614 | "postFuncBody": null |
1615 | + }, | ||
1616 | + { | ||
1617 | + "name": "fw_url", | ||
1618 | + "type": "attribute", | ||
1619 | + "label": "fw_url", | ||
1620 | + "color": "#e91e63", | ||
1621 | + "settings": { | ||
1622 | + "columnWidth": "0px", | ||
1623 | + "useCellStyleFunction": false, | ||
1624 | + "cellStyleFunction": "", | ||
1625 | + "useCellContentFunction": false, | ||
1626 | + "cellContentFunction": "", | ||
1627 | + "defaultColumnVisibility": "hidden", | ||
1628 | + "columnSelectionToDisplay": "disabled" | ||
1629 | + }, | ||
1630 | + "_hash": 0.4204673738685043, | ||
1631 | + "units": null, | ||
1632 | + "decimals": null, | ||
1633 | + "funcBody": null, | ||
1634 | + "usePostProcessing": null, | ||
1635 | + "postFuncBody": null | ||
1552 | } | 1636 | } |
1553 | ] | 1637 | ] |
1554 | } | 1638 | } |
@@ -1575,23 +1659,23 @@ | @@ -1575,23 +1659,23 @@ | ||
1575 | "icon": "edit", | 1659 | "icon": "edit", |
1576 | "type": "customPretty", | 1660 | "type": "customPretty", |
1577 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | 1661 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", |
1578 | - "customCss": "", | 1662 | + "customCss": "form {\n min-width: 300px !important;\n}", |
1579 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | 1663 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", |
1580 | "customResources": [], | 1664 | "customResources": [], |
1581 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | 1665 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" |
1582 | }, | 1666 | }, |
1583 | { | 1667 | { |
1584 | - "name": "Download firware", | 1668 | + "name": "Download firmware", |
1585 | "icon": "file_download", | 1669 | "icon": "file_download", |
1586 | "type": "custom", | 1670 | "type": "custom", |
1587 | - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", | 1671 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1588 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" | 1672 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" |
1589 | }, | 1673 | }, |
1590 | { | 1674 | { |
1591 | - "name": "Copy checksum", | 1675 | + "name": "Copy checksum/URL", |
1592 | "icon": "content_copy", | 1676 | "icon": "content_copy", |
1593 | "type": "custom", | 1677 | "type": "custom", |
1594 | - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", | 1678 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1595 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | 1679 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" |
1596 | } | 1680 | } |
1597 | ] | 1681 | ] |
@@ -1825,6 +1909,27 @@ | @@ -1825,6 +1909,27 @@ | ||
1825 | "funcBody": null, | 1909 | "funcBody": null, |
1826 | "usePostProcessing": null, | 1910 | "usePostProcessing": null, |
1827 | "postFuncBody": null | 1911 | "postFuncBody": null |
1912 | + }, | ||
1913 | + { | ||
1914 | + "name": "fw_url", | ||
1915 | + "type": "attribute", | ||
1916 | + "label": "fw_url", | ||
1917 | + "color": "#e91e63", | ||
1918 | + "settings": { | ||
1919 | + "columnWidth": "0px", | ||
1920 | + "useCellStyleFunction": false, | ||
1921 | + "cellStyleFunction": "", | ||
1922 | + "useCellContentFunction": false, | ||
1923 | + "cellContentFunction": "", | ||
1924 | + "defaultColumnVisibility": "hidden", | ||
1925 | + "columnSelectionToDisplay": "disabled" | ||
1926 | + }, | ||
1927 | + "_hash": 0.4204673738685043, | ||
1928 | + "units": null, | ||
1929 | + "decimals": null, | ||
1930 | + "funcBody": null, | ||
1931 | + "usePostProcessing": null, | ||
1932 | + "postFuncBody": null | ||
1828 | } | 1933 | } |
1829 | ] | 1934 | ] |
1830 | } | 1935 | } |
@@ -1851,23 +1956,23 @@ | @@ -1851,23 +1956,23 @@ | ||
1851 | "icon": "edit", | 1956 | "icon": "edit", |
1852 | "type": "customPretty", | 1957 | "type": "customPretty", |
1853 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | 1958 | "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", |
1854 | - "customCss": "", | 1959 | + "customCss": "form {\n min-width: 300px !important;\n}", |
1855 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | 1960 | "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", |
1856 | "customResources": [], | 1961 | "customResources": [], |
1857 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | 1962 | "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" |
1858 | }, | 1963 | }, |
1859 | { | 1964 | { |
1860 | - "name": "Download firware", | 1965 | + "name": "Download firmware", |
1861 | "icon": "file_download", | 1966 | "icon": "file_download", |
1862 | "type": "custom", | 1967 | "type": "custom", |
1863 | - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", | 1968 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1864 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" | 1969 | "id": "12533058-42f6-e75f-620c-219c48d01ec0" |
1865 | }, | 1970 | }, |
1866 | { | 1971 | { |
1867 | - "name": "Copy checksum", | 1972 | + "name": "Copy checksum/URL", |
1868 | "icon": "content_copy", | 1973 | "icon": "content_copy", |
1869 | "type": "custom", | 1974 | "type": "custom", |
1870 | - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", | 1975 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", |
1871 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | 1976 | "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" |
1872 | } | 1977 | } |
1873 | ] | 1978 | ] |
@@ -1936,7 +2041,7 @@ | @@ -1936,7 +2041,7 @@ | ||
1936 | } | 2041 | } |
1937 | }, | 2042 | }, |
1938 | "device_firmware_history": { | 2043 | "device_firmware_history": { |
1939 | - "name": "Device Firmware history", | 2044 | + "name": "Firmware history: ${entityName}", |
1940 | "root": false, | 2045 | "root": false, |
1941 | "layouts": { | 2046 | "layouts": { |
1942 | "main": { | 2047 | "main": { |
@@ -2379,7 +2484,8 @@ | @@ -2379,7 +2484,8 @@ | ||
2379 | "titleColor": "rgba(0,0,0,0.870588)", | 2484 | "titleColor": "rgba(0,0,0,0.870588)", |
2380 | "showFilters": true, | 2485 | "showFilters": true, |
2381 | "showDashboardLogo": false, | 2486 | "showDashboardLogo": false, |
2382 | - "dashboardLogoUrl": null | 2487 | + "dashboardLogoUrl": null, |
2488 | + "showUpdateDashboardImage": false | ||
2383 | } | 2489 | } |
2384 | }, | 2490 | }, |
2385 | "name": "Firmware" | 2491 | "name": "Firmware" |
1 | +{ | ||
2 | + "title": "Software", | ||
3 | + "image": null, | ||
4 | + "configuration": { | ||
5 | + "description": "", | ||
6 | + "widgets": { | ||
7 | + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { | ||
8 | + "isSystemType": true, | ||
9 | + "bundleAlias": "cards", | ||
10 | + "typeAlias": "entities_table", | ||
11 | + "type": "latest", | ||
12 | + "title": "New widget", | ||
13 | + "image": null, | ||
14 | + "description": null, | ||
15 | + "sizeX": 7.5, | ||
16 | + "sizeY": 6.5, | ||
17 | + "config": { | ||
18 | + "timewindow": { | ||
19 | + "realtime": { | ||
20 | + "interval": 1000, | ||
21 | + "timewindowMs": 86400000 | ||
22 | + }, | ||
23 | + "aggregation": { | ||
24 | + "type": "NONE", | ||
25 | + "limit": 200 | ||
26 | + } | ||
27 | + }, | ||
28 | + "showTitle": true, | ||
29 | + "backgroundColor": "rgb(255, 255, 255)", | ||
30 | + "color": "rgba(0, 0, 0, 0.87)", | ||
31 | + "padding": "4px", | ||
32 | + "settings": { | ||
33 | + "enableSearch": true, | ||
34 | + "displayPagination": true, | ||
35 | + "defaultPageSize": 10, | ||
36 | + "defaultSortOrder": "entityLabel", | ||
37 | + "displayEntityName": false, | ||
38 | + "displayEntityType": false, | ||
39 | + "enableSelectColumnDisplay": false, | ||
40 | + "enableStickyHeader": true, | ||
41 | + "enableStickyAction": false, | ||
42 | + "entitiesTitle": "Devices", | ||
43 | + "displayEntityLabel": true, | ||
44 | + "entityLabelColumnTitle": "Device" | ||
45 | + }, | ||
46 | + "title": "New Entities table", | ||
47 | + "dropShadow": true, | ||
48 | + "enableFullscreen": true, | ||
49 | + "titleStyle": { | ||
50 | + "fontSize": "16px", | ||
51 | + "fontWeight": 400, | ||
52 | + "padding": "5px 10px 5px 10px" | ||
53 | + }, | ||
54 | + "useDashboardTimewindow": false, | ||
55 | + "showLegend": false, | ||
56 | + "datasources": [ | ||
57 | + { | ||
58 | + "type": "entity", | ||
59 | + "name": null, | ||
60 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
61 | + "filterId": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", | ||
62 | + "dataKeys": [ | ||
63 | + { | ||
64 | + "name": "current_sw_title", | ||
65 | + "type": "timeseries", | ||
66 | + "label": "Current SW title", | ||
67 | + "color": "#2196f3", | ||
68 | + "settings": { | ||
69 | + "columnWidth": "0px", | ||
70 | + "useCellStyleFunction": false, | ||
71 | + "cellStyleFunction": "", | ||
72 | + "useCellContentFunction": false, | ||
73 | + "defaultColumnVisibility": "visible", | ||
74 | + "columnSelectionToDisplay": "enabled" | ||
75 | + }, | ||
76 | + "_hash": 0.09545533885166413, | ||
77 | + "units": null, | ||
78 | + "decimals": null, | ||
79 | + "funcBody": null, | ||
80 | + "usePostProcessing": null, | ||
81 | + "postFuncBody": null | ||
82 | + }, | ||
83 | + { | ||
84 | + "name": "current_sw_version", | ||
85 | + "type": "timeseries", | ||
86 | + "label": "Current SW version", | ||
87 | + "color": "#4caf50", | ||
88 | + "settings": { | ||
89 | + "columnWidth": "0px", | ||
90 | + "useCellStyleFunction": false, | ||
91 | + "cellStyleFunction": "", | ||
92 | + "useCellContentFunction": false, | ||
93 | + "defaultColumnVisibility": "visible", | ||
94 | + "columnSelectionToDisplay": "enabled" | ||
95 | + }, | ||
96 | + "_hash": 0.7206056602328659, | ||
97 | + "units": null, | ||
98 | + "decimals": null, | ||
99 | + "funcBody": null, | ||
100 | + "usePostProcessing": null, | ||
101 | + "postFuncBody": null | ||
102 | + }, | ||
103 | + { | ||
104 | + "name": "target_sw_title", | ||
105 | + "type": "timeseries", | ||
106 | + "label": "Target SW title", | ||
107 | + "color": "#ffc107", | ||
108 | + "settings": { | ||
109 | + "columnWidth": "0px", | ||
110 | + "useCellStyleFunction": false, | ||
111 | + "cellStyleFunction": "", | ||
112 | + "useCellContentFunction": false, | ||
113 | + "defaultColumnVisibility": "visible", | ||
114 | + "columnSelectionToDisplay": "enabled" | ||
115 | + }, | ||
116 | + "_hash": 0.9934225682766313, | ||
117 | + "units": null, | ||
118 | + "decimals": null, | ||
119 | + "funcBody": null, | ||
120 | + "usePostProcessing": null, | ||
121 | + "postFuncBody": null | ||
122 | + }, | ||
123 | + { | ||
124 | + "name": "target_sw_version", | ||
125 | + "type": "timeseries", | ||
126 | + "label": "Target SW version", | ||
127 | + "color": "#607d8b", | ||
128 | + "settings": { | ||
129 | + "columnWidth": "0px", | ||
130 | + "useCellStyleFunction": false, | ||
131 | + "cellStyleFunction": "", | ||
132 | + "useCellContentFunction": false, | ||
133 | + "cellContentFunction": "", | ||
134 | + "defaultColumnVisibility": "visible", | ||
135 | + "columnSelectionToDisplay": "enabled" | ||
136 | + }, | ||
137 | + "_hash": 0.5251724416842531, | ||
138 | + "units": null, | ||
139 | + "decimals": null, | ||
140 | + "funcBody": null, | ||
141 | + "usePostProcessing": null, | ||
142 | + "postFuncBody": null | ||
143 | + }, | ||
144 | + { | ||
145 | + "name": "target_sw_ts", | ||
146 | + "type": "timeseries", | ||
147 | + "label": "Target SW set time", | ||
148 | + "color": "#e91e63", | ||
149 | + "settings": { | ||
150 | + "columnWidth": "0px", | ||
151 | + "useCellStyleFunction": false, | ||
152 | + "cellStyleFunction": "", | ||
153 | + "useCellContentFunction": true, | ||
154 | + "defaultColumnVisibility": "visible", | ||
155 | + "columnSelectionToDisplay": "enabled", | ||
156 | + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" | ||
157 | + }, | ||
158 | + "_hash": 0.31823244858578237, | ||
159 | + "units": null, | ||
160 | + "decimals": null, | ||
161 | + "funcBody": null, | ||
162 | + "usePostProcessing": null, | ||
163 | + "postFuncBody": null | ||
164 | + }, | ||
165 | + { | ||
166 | + "name": "sw_state", | ||
167 | + "type": "timeseries", | ||
168 | + "label": "Progress", | ||
169 | + "color": "#9c27b0", | ||
170 | + "settings": { | ||
171 | + "columnWidth": "30%", | ||
172 | + "useCellStyleFunction": true, | ||
173 | + "useCellContentFunction": true, | ||
174 | + "defaultColumnVisibility": "visible", | ||
175 | + "columnSelectionToDisplay": "enabled", | ||
176 | + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", | ||
177 | + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}" | ||
178 | + }, | ||
179 | + "_hash": 0.8174211757846257, | ||
180 | + "units": null, | ||
181 | + "decimals": null, | ||
182 | + "funcBody": null, | ||
183 | + "usePostProcessing": null, | ||
184 | + "postFuncBody": null | ||
185 | + }, | ||
186 | + { | ||
187 | + "name": "sw_state", | ||
188 | + "type": "timeseries", | ||
189 | + "label": "Status", | ||
190 | + "color": "#f44336", | ||
191 | + "settings": { | ||
192 | + "columnWidth": "130px", | ||
193 | + "useCellStyleFunction": true, | ||
194 | + "useCellContentFunction": true, | ||
195 | + "defaultColumnVisibility": "visible", | ||
196 | + "columnSelectionToDisplay": "enabled", | ||
197 | + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", | ||
198 | + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';" | ||
199 | + }, | ||
200 | + "_hash": 0.7764426948615217, | ||
201 | + "units": null, | ||
202 | + "decimals": null, | ||
203 | + "funcBody": null, | ||
204 | + "usePostProcessing": null, | ||
205 | + "postFuncBody": null | ||
206 | + }, | ||
207 | + { | ||
208 | + "name": "sw_checksum", | ||
209 | + "type": "attribute", | ||
210 | + "label": "sw_checksum", | ||
211 | + "color": "#3f51b5", | ||
212 | + "settings": { | ||
213 | + "columnWidth": "0px", | ||
214 | + "useCellStyleFunction": false, | ||
215 | + "cellStyleFunction": "", | ||
216 | + "useCellContentFunction": false, | ||
217 | + "defaultColumnVisibility": "hidden", | ||
218 | + "columnSelectionToDisplay": "disabled" | ||
219 | + }, | ||
220 | + "_hash": 0.5594087842471693, | ||
221 | + "units": null, | ||
222 | + "decimals": null, | ||
223 | + "funcBody": null, | ||
224 | + "usePostProcessing": null, | ||
225 | + "postFuncBody": null | ||
226 | + }, | ||
227 | + { | ||
228 | + "name": "sw_url", | ||
229 | + "type": "attribute", | ||
230 | + "label": "sw_url", | ||
231 | + "color": "#e91e63", | ||
232 | + "settings": { | ||
233 | + "columnWidth": "0px", | ||
234 | + "useCellStyleFunction": false, | ||
235 | + "cellStyleFunction": "", | ||
236 | + "useCellContentFunction": false, | ||
237 | + "cellContentFunction": "", | ||
238 | + "defaultColumnVisibility": "hidden", | ||
239 | + "columnSelectionToDisplay": "disabled" | ||
240 | + }, | ||
241 | + "_hash": 0.3355829384124256, | ||
242 | + "units": null, | ||
243 | + "decimals": null, | ||
244 | + "funcBody": null, | ||
245 | + "usePostProcessing": null, | ||
246 | + "postFuncBody": null | ||
247 | + } | ||
248 | + ] | ||
249 | + } | ||
250 | + ], | ||
251 | + "actions": { | ||
252 | + "actionCellButton": [ | ||
253 | + { | ||
254 | + "name": "History software update", | ||
255 | + "icon": "history", | ||
256 | + "type": "openDashboardState", | ||
257 | + "targetDashboardStateId": "device_software_history", | ||
258 | + "setEntityId": true, | ||
259 | + "stateEntityParamName": null, | ||
260 | + "openInSeparateDialog": false, | ||
261 | + "dialogTitle": "", | ||
262 | + "dialogHideDashboardToolbar": true, | ||
263 | + "dialogWidth": null, | ||
264 | + "dialogHeight": null, | ||
265 | + "openRightLayout": false, | ||
266 | + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" | ||
267 | + }, | ||
268 | + { | ||
269 | + "name": "Edit software", | ||
270 | + "icon": "edit", | ||
271 | + "type": "customPretty", | ||
272 | + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | ||
273 | + "customCss": "form {\n min-width: 300px !important;\n}", | ||
274 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | ||
275 | + "customResources": [], | ||
276 | + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | ||
277 | + }, | ||
278 | + { | ||
279 | + "name": "Download software", | ||
280 | + "icon": "file_download", | ||
281 | + "type": "custom", | ||
282 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
283 | + "id": "12533058-42f6-e75f-620c-219c48d01ec0" | ||
284 | + }, | ||
285 | + { | ||
286 | + "name": "Copy checksum/URL", | ||
287 | + "icon": "content_copy", | ||
288 | + "type": "custom", | ||
289 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
290 | + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | ||
291 | + } | ||
292 | + ] | ||
293 | + }, | ||
294 | + "showTitleIcon": false, | ||
295 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
296 | + "iconSize": "24px", | ||
297 | + "titleTooltip": "", | ||
298 | + "widgetStyle": {} | ||
299 | + }, | ||
300 | + "row": 0, | ||
301 | + "col": 0, | ||
302 | + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" | ||
303 | + }, | ||
304 | + "100b756c-0082-6505-3ae1-3603e6deea48": { | ||
305 | + "isSystemType": true, | ||
306 | + "bundleAlias": "cards", | ||
307 | + "typeAlias": "timeseries_table", | ||
308 | + "type": "timeseries", | ||
309 | + "title": "New widget", | ||
310 | + "image": null, | ||
311 | + "description": null, | ||
312 | + "sizeX": 8, | ||
313 | + "sizeY": 6.5, | ||
314 | + "config": { | ||
315 | + "datasources": [ | ||
316 | + { | ||
317 | + "type": "entity", | ||
318 | + "name": null, | ||
319 | + "entityAliasId": "19f41c21-d9af-e666-8f50-e1748778f955", | ||
320 | + "filterId": null, | ||
321 | + "dataKeys": [ | ||
322 | + { | ||
323 | + "name": "current_sw_title", | ||
324 | + "type": "timeseries", | ||
325 | + "label": "Current software title", | ||
326 | + "color": "#2196f3", | ||
327 | + "settings": { | ||
328 | + "useCellStyleFunction": false, | ||
329 | + "cellStyleFunction": "", | ||
330 | + "useCellContentFunction": false, | ||
331 | + "cellContentFunction": "" | ||
332 | + }, | ||
333 | + "_hash": 0.5978079905579401, | ||
334 | + "units": null, | ||
335 | + "decimals": null, | ||
336 | + "funcBody": null, | ||
337 | + "usePostProcessing": null, | ||
338 | + "postFuncBody": null | ||
339 | + }, | ||
340 | + { | ||
341 | + "name": "current_sw_version", | ||
342 | + "type": "timeseries", | ||
343 | + "label": "Current software version", | ||
344 | + "color": "#4caf50", | ||
345 | + "settings": { | ||
346 | + "useCellStyleFunction": false, | ||
347 | + "cellStyleFunction": "", | ||
348 | + "useCellContentFunction": false, | ||
349 | + "cellContentFunction": "" | ||
350 | + }, | ||
351 | + "_hash": 0.027392025058568192, | ||
352 | + "units": null, | ||
353 | + "decimals": null, | ||
354 | + "funcBody": null, | ||
355 | + "usePostProcessing": null, | ||
356 | + "postFuncBody": null | ||
357 | + }, | ||
358 | + { | ||
359 | + "name": "target_sw_title", | ||
360 | + "type": "timeseries", | ||
361 | + "label": "Target software title", | ||
362 | + "color": "#f44336", | ||
363 | + "settings": { | ||
364 | + "useCellStyleFunction": false, | ||
365 | + "cellStyleFunction": "", | ||
366 | + "useCellContentFunction": false, | ||
367 | + "cellContentFunction": "" | ||
368 | + }, | ||
369 | + "_hash": 0.9496350796287059, | ||
370 | + "units": null, | ||
371 | + "decimals": null, | ||
372 | + "funcBody": null, | ||
373 | + "usePostProcessing": null, | ||
374 | + "postFuncBody": null | ||
375 | + }, | ||
376 | + { | ||
377 | + "name": "target_sw_version", | ||
378 | + "type": "timeseries", | ||
379 | + "label": "Target software version", | ||
380 | + "color": "#ffc107", | ||
381 | + "settings": { | ||
382 | + "useCellStyleFunction": false, | ||
383 | + "cellStyleFunction": "", | ||
384 | + "useCellContentFunction": false, | ||
385 | + "cellContentFunction": "" | ||
386 | + }, | ||
387 | + "_hash": 0.6734152252264187, | ||
388 | + "units": null, | ||
389 | + "decimals": null, | ||
390 | + "funcBody": null, | ||
391 | + "usePostProcessing": null, | ||
392 | + "postFuncBody": null | ||
393 | + }, | ||
394 | + { | ||
395 | + "name": "sw_state", | ||
396 | + "type": "timeseries", | ||
397 | + "label": "Status", | ||
398 | + "color": "#607d8b", | ||
399 | + "settings": { | ||
400 | + "useCellStyleFunction": false, | ||
401 | + "cellStyleFunction": "", | ||
402 | + "useCellContentFunction": false, | ||
403 | + "cellContentFunction": "" | ||
404 | + }, | ||
405 | + "_hash": 0.2983399718643074, | ||
406 | + "units": null, | ||
407 | + "decimals": null, | ||
408 | + "funcBody": null, | ||
409 | + "usePostProcessing": true, | ||
410 | + "postFuncBody": "function capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\nif (value !== '') {\n return capitalize(value);\n}\nreturn value;" | ||
411 | + } | ||
412 | + ] | ||
413 | + } | ||
414 | + ], | ||
415 | + "timewindow": { | ||
416 | + "hideInterval": false, | ||
417 | + "hideAggregation": false, | ||
418 | + "hideAggInterval": false, | ||
419 | + "hideTimezone": false, | ||
420 | + "selectedTab": 0, | ||
421 | + "realtime": { | ||
422 | + "realtimeType": 0, | ||
423 | + "timewindowMs": 2592000000, | ||
424 | + "quickInterval": "CURRENT_DAY", | ||
425 | + "interval": 1000 | ||
426 | + }, | ||
427 | + "aggregation": { | ||
428 | + "type": "NONE", | ||
429 | + "limit": 200 | ||
430 | + } | ||
431 | + }, | ||
432 | + "showTitle": false, | ||
433 | + "backgroundColor": "rgb(255, 255, 255)", | ||
434 | + "color": "rgba(0, 0, 0, 0.87)", | ||
435 | + "padding": "8px", | ||
436 | + "settings": { | ||
437 | + "showTimestamp": true, | ||
438 | + "displayPagination": true, | ||
439 | + "defaultPageSize": 10, | ||
440 | + "enableSearch": true, | ||
441 | + "enableStickyHeader": true, | ||
442 | + "enableStickyAction": true | ||
443 | + }, | ||
444 | + "title": "Software history", | ||
445 | + "dropShadow": false, | ||
446 | + "enableFullscreen": false, | ||
447 | + "titleStyle": { | ||
448 | + "fontSize": "16px", | ||
449 | + "fontWeight": 400, | ||
450 | + "padding": "5px 10px 5px 10px" | ||
451 | + }, | ||
452 | + "useDashboardTimewindow": false, | ||
453 | + "showLegend": false, | ||
454 | + "widgetStyle": {}, | ||
455 | + "actions": {}, | ||
456 | + "showTitleIcon": false, | ||
457 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
458 | + "iconSize": "24px", | ||
459 | + "displayTimewindow": true, | ||
460 | + "titleTooltip": "" | ||
461 | + }, | ||
462 | + "row": 0, | ||
463 | + "col": 0, | ||
464 | + "id": "100b756c-0082-6505-3ae1-3603e6deea48" | ||
465 | + }, | ||
466 | + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { | ||
467 | + "isSystemType": true, | ||
468 | + "bundleAlias": "cards", | ||
469 | + "typeAlias": "html_value_card", | ||
470 | + "type": "latest", | ||
471 | + "title": "New widget", | ||
472 | + "sizeX": 8, | ||
473 | + "sizeY": 3, | ||
474 | + "config": { | ||
475 | + "datasources": [ | ||
476 | + { | ||
477 | + "type": "entityCount", | ||
478 | + "name": "", | ||
479 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
480 | + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", | ||
481 | + "dataKeys": [ | ||
482 | + { | ||
483 | + "name": "count", | ||
484 | + "type": "count", | ||
485 | + "label": "waitingDevicesNumber", | ||
486 | + "color": "#4caf50", | ||
487 | + "settings": {}, | ||
488 | + "_hash": 0.7404827038869322, | ||
489 | + "units": null, | ||
490 | + "decimals": null, | ||
491 | + "funcBody": null, | ||
492 | + "usePostProcessing": null, | ||
493 | + "postFuncBody": null | ||
494 | + } | ||
495 | + ] | ||
496 | + } | ||
497 | + ], | ||
498 | + "timewindow": { | ||
499 | + "realtime": { | ||
500 | + "timewindowMs": 60000 | ||
501 | + } | ||
502 | + }, | ||
503 | + "showTitle": false, | ||
504 | + "backgroundColor": "#fff", | ||
505 | + "color": "rgba(0, 0, 0, 0.87)", | ||
506 | + "padding": "0px", | ||
507 | + "settings": { | ||
508 | + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"24\" height=\"40\" viewBox=\"0 0 24 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M0 0V12H0.0200005L0 12.02L8 20L0 28L0.0200005 28.02H0V40H24V28.02H23.98L24 28L16 20L24 12.02L23.98 12H24V0H0ZM20 29V36H4V29L12 21L20 29ZM12 19L4 11V4H20V11L12 19Z\" fill=\"black\"/>\n</svg>\n'>\n <div class='value' id=\"activeDevices\">\n ${waitingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Waiting\n </div>\n </div>\n</div>", | ||
509 | + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" | ||
510 | + }, | ||
511 | + "title": "New HTML Value Card", | ||
512 | + "dropShadow": true, | ||
513 | + "enableFullscreen": false, | ||
514 | + "widgetStyle": {}, | ||
515 | + "titleStyle": { | ||
516 | + "fontSize": "16px", | ||
517 | + "fontWeight": 400 | ||
518 | + }, | ||
519 | + "useDashboardTimewindow": true, | ||
520 | + "showLegend": false, | ||
521 | + "actions": { | ||
522 | + "elementClick": [ | ||
523 | + { | ||
524 | + "name": "activeDevices", | ||
525 | + "icon": "more_horiz", | ||
526 | + "type": "openDashboardState", | ||
527 | + "targetDashboardStateId": "device_waiting", | ||
528 | + "setEntityId": false, | ||
529 | + "stateEntityParamName": null, | ||
530 | + "openInSeparateDialog": false, | ||
531 | + "dialogTitle": "", | ||
532 | + "dialogHideDashboardToolbar": true, | ||
533 | + "dialogWidth": null, | ||
534 | + "dialogHeight": null, | ||
535 | + "openRightLayout": false, | ||
536 | + "id": "4d9a77a2-f0a5-690c-a83b-b0e940be788c" | ||
537 | + } | ||
538 | + ] | ||
539 | + }, | ||
540 | + "showTitleIcon": false, | ||
541 | + "titleIcon": null, | ||
542 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
543 | + "iconSize": "24px", | ||
544 | + "titleTooltip": "", | ||
545 | + "enableDataExport": false, | ||
546 | + "displayTimewindow": true | ||
547 | + }, | ||
548 | + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" | ||
549 | + }, | ||
550 | + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { | ||
551 | + "isSystemType": true, | ||
552 | + "bundleAlias": "cards", | ||
553 | + "typeAlias": "html_value_card", | ||
554 | + "type": "latest", | ||
555 | + "title": "New widget", | ||
556 | + "sizeX": 8, | ||
557 | + "sizeY": 3, | ||
558 | + "config": { | ||
559 | + "datasources": [ | ||
560 | + { | ||
561 | + "type": "entityCount", | ||
562 | + "name": "", | ||
563 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
564 | + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", | ||
565 | + "dataKeys": [ | ||
566 | + { | ||
567 | + "name": "count", | ||
568 | + "type": "count", | ||
569 | + "label": "updatingDevicesNumber", | ||
570 | + "color": "#4caf50", | ||
571 | + "settings": {}, | ||
572 | + "_hash": 0.7404827038869322, | ||
573 | + "units": null, | ||
574 | + "decimals": null, | ||
575 | + "funcBody": null, | ||
576 | + "usePostProcessing": null, | ||
577 | + "postFuncBody": null | ||
578 | + } | ||
579 | + ] | ||
580 | + } | ||
581 | + ], | ||
582 | + "timewindow": { | ||
583 | + "realtime": { | ||
584 | + "timewindowMs": 60000 | ||
585 | + } | ||
586 | + }, | ||
587 | + "showTitle": false, | ||
588 | + "backgroundColor": "#fff", | ||
589 | + "color": "rgba(0, 0, 0, 0.87)", | ||
590 | + "padding": "0px", | ||
591 | + "settings": { | ||
592 | + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"36\" height=\"36\" viewBox=\"0 0 36 36\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M36 14.24H22.44L27.92 8.6C22.46 3.2 13.62 3 8.16001 8.4C2.70001 13.82 2.70001 22.56 8.16001 27.98C13.62 33.4 22.46 33.4 27.92 27.98C30.64 25.3 32 22.16 32 18.2H36C36 22.16 34.24 27.3 30.72 30.78C23.7 37.74 12.3 37.74 5.28001 30.78C-1.71999 23.84 -1.77999 12.56 5.24001 5.62C12.26 -1.32 23.52 -1.32 30.54 5.62L36 0V14.24ZM19 10V18.5L26 22.66L24.56 25.08L16 20V10H19Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updating\n </div>\n </div>\n</div>", | ||
593 | + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" | ||
594 | + }, | ||
595 | + "title": "New HTML Value Card", | ||
596 | + "dropShadow": true, | ||
597 | + "enableFullscreen": false, | ||
598 | + "widgetStyle": {}, | ||
599 | + "titleStyle": { | ||
600 | + "fontSize": "16px", | ||
601 | + "fontWeight": 400 | ||
602 | + }, | ||
603 | + "useDashboardTimewindow": true, | ||
604 | + "showLegend": false, | ||
605 | + "actions": { | ||
606 | + "elementClick": [ | ||
607 | + { | ||
608 | + "name": "activeDevices", | ||
609 | + "icon": "more_horiz", | ||
610 | + "type": "openDashboardState", | ||
611 | + "targetDashboardStateId": "device_updating", | ||
612 | + "setEntityId": false, | ||
613 | + "stateEntityParamName": null, | ||
614 | + "openInSeparateDialog": false, | ||
615 | + "dialogTitle": "", | ||
616 | + "dialogHideDashboardToolbar": true, | ||
617 | + "dialogWidth": null, | ||
618 | + "dialogHeight": null, | ||
619 | + "openRightLayout": false, | ||
620 | + "id": "57d39904-2350-b29b-78ed-56b8268814cb" | ||
621 | + } | ||
622 | + ] | ||
623 | + }, | ||
624 | + "showTitleIcon": false, | ||
625 | + "titleIcon": null, | ||
626 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
627 | + "iconSize": "24px", | ||
628 | + "titleTooltip": "", | ||
629 | + "enableDataExport": false, | ||
630 | + "displayTimewindow": true | ||
631 | + }, | ||
632 | + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" | ||
633 | + }, | ||
634 | + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { | ||
635 | + "isSystemType": true, | ||
636 | + "bundleAlias": "cards", | ||
637 | + "typeAlias": "html_value_card", | ||
638 | + "type": "latest", | ||
639 | + "title": "New widget", | ||
640 | + "sizeX": 8, | ||
641 | + "sizeY": 3, | ||
642 | + "config": { | ||
643 | + "datasources": [ | ||
644 | + { | ||
645 | + "type": "entityCount", | ||
646 | + "name": "", | ||
647 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
648 | + "filterId": "6044e198-df64-cd76-f339-696f220c4943", | ||
649 | + "dataKeys": [ | ||
650 | + { | ||
651 | + "name": "count", | ||
652 | + "type": "count", | ||
653 | + "label": "updatedDevicesNumber", | ||
654 | + "color": "#4caf50", | ||
655 | + "settings": {}, | ||
656 | + "_hash": 0.7404827038869322, | ||
657 | + "units": null, | ||
658 | + "decimals": null, | ||
659 | + "funcBody": null, | ||
660 | + "usePostProcessing": null, | ||
661 | + "postFuncBody": null | ||
662 | + } | ||
663 | + ] | ||
664 | + } | ||
665 | + ], | ||
666 | + "timewindow": { | ||
667 | + "realtime": { | ||
668 | + "timewindowMs": 60000 | ||
669 | + } | ||
670 | + }, | ||
671 | + "showTitle": false, | ||
672 | + "backgroundColor": "#fff", | ||
673 | + "color": "rgba(0, 0, 0, 0.87)", | ||
674 | + "padding": "0px", | ||
675 | + "settings": { | ||
676 | + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"34\" height=\"32\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/>\n<path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatedDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updated\n </div>\n </div>\n</div>", | ||
677 | + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" | ||
678 | + }, | ||
679 | + "title": "New HTML Value Card", | ||
680 | + "dropShadow": true, | ||
681 | + "enableFullscreen": false, | ||
682 | + "widgetStyle": {}, | ||
683 | + "titleStyle": { | ||
684 | + "fontSize": "16px", | ||
685 | + "fontWeight": 400 | ||
686 | + }, | ||
687 | + "useDashboardTimewindow": true, | ||
688 | + "showLegend": false, | ||
689 | + "actions": { | ||
690 | + "elementClick": [ | ||
691 | + { | ||
692 | + "name": "activeDevices", | ||
693 | + "icon": "more_horiz", | ||
694 | + "type": "openDashboardState", | ||
695 | + "targetDashboardStateId": "device_updated", | ||
696 | + "setEntityId": false, | ||
697 | + "stateEntityParamName": null, | ||
698 | + "openInSeparateDialog": false, | ||
699 | + "dialogTitle": "", | ||
700 | + "dialogHideDashboardToolbar": true, | ||
701 | + "dialogWidth": null, | ||
702 | + "dialogHeight": null, | ||
703 | + "openRightLayout": false, | ||
704 | + "id": "d787c212-8c56-34f0-349a-5aae2ffd1eae" | ||
705 | + } | ||
706 | + ] | ||
707 | + }, | ||
708 | + "showTitleIcon": false, | ||
709 | + "titleIcon": null, | ||
710 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
711 | + "iconSize": "24px", | ||
712 | + "titleTooltip": "", | ||
713 | + "enableDataExport": false, | ||
714 | + "displayTimewindow": true | ||
715 | + }, | ||
716 | + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" | ||
717 | + }, | ||
718 | + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { | ||
719 | + "isSystemType": true, | ||
720 | + "bundleAlias": "cards", | ||
721 | + "typeAlias": "html_value_card", | ||
722 | + "type": "latest", | ||
723 | + "title": "New widget", | ||
724 | + "sizeX": 8, | ||
725 | + "sizeY": 3, | ||
726 | + "config": { | ||
727 | + "datasources": [ | ||
728 | + { | ||
729 | + "type": "entityCount", | ||
730 | + "name": "", | ||
731 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
732 | + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", | ||
733 | + "dataKeys": [ | ||
734 | + { | ||
735 | + "name": "count", | ||
736 | + "type": "count", | ||
737 | + "label": "updatingDevicesNumber", | ||
738 | + "color": "#4caf50", | ||
739 | + "settings": {}, | ||
740 | + "_hash": 0.7404827038869322, | ||
741 | + "units": null, | ||
742 | + "decimals": null, | ||
743 | + "funcBody": null, | ||
744 | + "usePostProcessing": null, | ||
745 | + "postFuncBody": null | ||
746 | + } | ||
747 | + ] | ||
748 | + } | ||
749 | + ], | ||
750 | + "timewindow": { | ||
751 | + "realtime": { | ||
752 | + "timewindowMs": 60000 | ||
753 | + } | ||
754 | + }, | ||
755 | + "showTitle": false, | ||
756 | + "backgroundColor": "#fff", | ||
757 | + "color": "rgba(0, 0, 0, 0.87)", | ||
758 | + "padding": "0px", | ||
759 | + "settings": { | ||
760 | + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <div class=\"container-svg\" id=\"activeDevices\">\n <svg viewBox=\"0 0 24 24\" id=\"activeDevices\">\n <path id=\"activeDevices\" fill=\"currentColor\" d=\"M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z\" />\n </svg>\n </div>\n <div class='value error_software_failed_count' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <script type=\"text/javascript\">\n function init() {\n var counter = $('.error_software_failed_count');\n var value = +counter.text();\n if(value) {\n counter.css('color', '#D93025');\n }\n };\n init();\n </script>\n <div class='description' id=\"activeDevices\">\n Device Failed\n </div>\n </div>\n</div>", | ||
761 | + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .container-svg {\n height: 40px;\n width: 40px;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .container-svg {\n height: 28px;\n width: 28px;\n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" | ||
762 | + }, | ||
763 | + "title": "New HTML Value Card", | ||
764 | + "dropShadow": true, | ||
765 | + "enableFullscreen": false, | ||
766 | + "widgetStyle": {}, | ||
767 | + "titleStyle": { | ||
768 | + "fontSize": "16px", | ||
769 | + "fontWeight": 400 | ||
770 | + }, | ||
771 | + "useDashboardTimewindow": true, | ||
772 | + "showLegend": false, | ||
773 | + "actions": { | ||
774 | + "elementClick": [ | ||
775 | + { | ||
776 | + "name": "activeDevices", | ||
777 | + "icon": "more_horiz", | ||
778 | + "type": "openDashboardState", | ||
779 | + "targetDashboardStateId": "device_error", | ||
780 | + "setEntityId": false, | ||
781 | + "stateEntityParamName": null, | ||
782 | + "openInSeparateDialog": false, | ||
783 | + "dialogTitle": "", | ||
784 | + "dialogHideDashboardToolbar": true, | ||
785 | + "dialogWidth": null, | ||
786 | + "dialogHeight": null, | ||
787 | + "openRightLayout": false, | ||
788 | + "id": "0b3d2887-9929-84d5-3795-0763dca15cba" | ||
789 | + } | ||
790 | + ] | ||
791 | + }, | ||
792 | + "showTitleIcon": false, | ||
793 | + "titleIcon": null, | ||
794 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
795 | + "iconSize": "24px", | ||
796 | + "titleTooltip": "", | ||
797 | + "enableDataExport": false, | ||
798 | + "displayTimewindow": true | ||
799 | + }, | ||
800 | + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" | ||
801 | + }, | ||
802 | + "21be08bb-ec90-f760-ad6f-e7678f12c401": { | ||
803 | + "isSystemType": true, | ||
804 | + "bundleAlias": "cards", | ||
805 | + "typeAlias": "entities_table", | ||
806 | + "type": "latest", | ||
807 | + "title": "New widget", | ||
808 | + "image": null, | ||
809 | + "description": null, | ||
810 | + "sizeX": 7.5, | ||
811 | + "sizeY": 6.5, | ||
812 | + "config": { | ||
813 | + "timewindow": { | ||
814 | + "realtime": { | ||
815 | + "interval": 1000, | ||
816 | + "timewindowMs": 86400000 | ||
817 | + }, | ||
818 | + "aggregation": { | ||
819 | + "type": "NONE", | ||
820 | + "limit": 200 | ||
821 | + } | ||
822 | + }, | ||
823 | + "showTitle": true, | ||
824 | + "backgroundColor": "rgb(255, 255, 255)", | ||
825 | + "color": "rgba(0, 0, 0, 0.87)", | ||
826 | + "padding": "4px", | ||
827 | + "settings": { | ||
828 | + "enableSearch": true, | ||
829 | + "displayPagination": true, | ||
830 | + "defaultPageSize": 10, | ||
831 | + "defaultSortOrder": "entityLabel", | ||
832 | + "displayEntityName": false, | ||
833 | + "displayEntityType": false, | ||
834 | + "enableSelectColumnDisplay": false, | ||
835 | + "enableStickyHeader": true, | ||
836 | + "enableStickyAction": true, | ||
837 | + "entitiesTitle": "Devices", | ||
838 | + "displayEntityLabel": true, | ||
839 | + "entityLabelColumnTitle": "Device" | ||
840 | + }, | ||
841 | + "title": "New Entities table", | ||
842 | + "dropShadow": true, | ||
843 | + "enableFullscreen": true, | ||
844 | + "titleStyle": { | ||
845 | + "fontSize": "16px", | ||
846 | + "fontWeight": 400, | ||
847 | + "padding": "5px 10px 5px 10px" | ||
848 | + }, | ||
849 | + "useDashboardTimewindow": false, | ||
850 | + "showLegend": false, | ||
851 | + "datasources": [ | ||
852 | + { | ||
853 | + "type": "entity", | ||
854 | + "name": null, | ||
855 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
856 | + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", | ||
857 | + "dataKeys": [ | ||
858 | + { | ||
859 | + "name": "current_sw_title", | ||
860 | + "type": "timeseries", | ||
861 | + "label": "Current SW title", | ||
862 | + "color": "#2196f3", | ||
863 | + "settings": { | ||
864 | + "columnWidth": "0px", | ||
865 | + "useCellStyleFunction": false, | ||
866 | + "cellStyleFunction": "", | ||
867 | + "useCellContentFunction": false, | ||
868 | + "defaultColumnVisibility": "visible", | ||
869 | + "columnSelectionToDisplay": "enabled" | ||
870 | + }, | ||
871 | + "_hash": 0.09545533885166413, | ||
872 | + "units": null, | ||
873 | + "decimals": null, | ||
874 | + "funcBody": null, | ||
875 | + "usePostProcessing": null, | ||
876 | + "postFuncBody": null | ||
877 | + }, | ||
878 | + { | ||
879 | + "name": "current_sw_version", | ||
880 | + "type": "timeseries", | ||
881 | + "label": "Current SW version", | ||
882 | + "color": "#4caf50", | ||
883 | + "settings": { | ||
884 | + "columnWidth": "0px", | ||
885 | + "useCellStyleFunction": false, | ||
886 | + "cellStyleFunction": "", | ||
887 | + "useCellContentFunction": false, | ||
888 | + "defaultColumnVisibility": "visible", | ||
889 | + "columnSelectionToDisplay": "enabled" | ||
890 | + }, | ||
891 | + "_hash": 0.7206056602328659, | ||
892 | + "units": null, | ||
893 | + "decimals": null, | ||
894 | + "funcBody": null, | ||
895 | + "usePostProcessing": null, | ||
896 | + "postFuncBody": null | ||
897 | + }, | ||
898 | + { | ||
899 | + "name": "target_sw_title", | ||
900 | + "type": "timeseries", | ||
901 | + "label": "Target SW title", | ||
902 | + "color": "#ffc107", | ||
903 | + "settings": { | ||
904 | + "columnWidth": "0px", | ||
905 | + "useCellStyleFunction": false, | ||
906 | + "cellStyleFunction": "", | ||
907 | + "useCellContentFunction": false, | ||
908 | + "defaultColumnVisibility": "visible", | ||
909 | + "columnSelectionToDisplay": "enabled" | ||
910 | + }, | ||
911 | + "_hash": 0.9934225682766313, | ||
912 | + "units": null, | ||
913 | + "decimals": null, | ||
914 | + "funcBody": null, | ||
915 | + "usePostProcessing": null, | ||
916 | + "postFuncBody": null | ||
917 | + }, | ||
918 | + { | ||
919 | + "name": "target_sw_version", | ||
920 | + "type": "timeseries", | ||
921 | + "label": "Target SW version", | ||
922 | + "color": "#607d8b", | ||
923 | + "settings": { | ||
924 | + "columnWidth": "0px", | ||
925 | + "useCellStyleFunction": false, | ||
926 | + "cellStyleFunction": "", | ||
927 | + "useCellContentFunction": false, | ||
928 | + "cellContentFunction": "", | ||
929 | + "defaultColumnVisibility": "visible", | ||
930 | + "columnSelectionToDisplay": "enabled" | ||
931 | + }, | ||
932 | + "_hash": 0.5251724416842531, | ||
933 | + "units": null, | ||
934 | + "decimals": null, | ||
935 | + "funcBody": null, | ||
936 | + "usePostProcessing": null, | ||
937 | + "postFuncBody": null | ||
938 | + }, | ||
939 | + { | ||
940 | + "name": "target_sw_ts", | ||
941 | + "type": "timeseries", | ||
942 | + "label": "Target SW set time", | ||
943 | + "color": "#e91e63", | ||
944 | + "settings": { | ||
945 | + "columnWidth": "0px", | ||
946 | + "useCellStyleFunction": false, | ||
947 | + "cellStyleFunction": "", | ||
948 | + "useCellContentFunction": true, | ||
949 | + "defaultColumnVisibility": "visible", | ||
950 | + "columnSelectionToDisplay": "enabled", | ||
951 | + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" | ||
952 | + }, | ||
953 | + "_hash": 0.31823244858578237, | ||
954 | + "units": null, | ||
955 | + "decimals": null, | ||
956 | + "funcBody": null, | ||
957 | + "usePostProcessing": null, | ||
958 | + "postFuncBody": null | ||
959 | + }, | ||
960 | + { | ||
961 | + "name": "sw_state", | ||
962 | + "type": "timeseries", | ||
963 | + "label": "Progress", | ||
964 | + "color": "#9c27b0", | ||
965 | + "settings": { | ||
966 | + "columnWidth": "30%", | ||
967 | + "useCellStyleFunction": true, | ||
968 | + "useCellContentFunction": true, | ||
969 | + "defaultColumnVisibility": "visible", | ||
970 | + "columnSelectionToDisplay": "enabled", | ||
971 | + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", | ||
972 | + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}" | ||
973 | + }, | ||
974 | + "_hash": 0.8174211757846257, | ||
975 | + "units": null, | ||
976 | + "decimals": null, | ||
977 | + "funcBody": null, | ||
978 | + "usePostProcessing": null, | ||
979 | + "postFuncBody": null | ||
980 | + }, | ||
981 | + { | ||
982 | + "name": "sw_state", | ||
983 | + "type": "timeseries", | ||
984 | + "label": "Status", | ||
985 | + "color": "#f44336", | ||
986 | + "settings": { | ||
987 | + "columnWidth": "130px", | ||
988 | + "useCellStyleFunction": true, | ||
989 | + "useCellContentFunction": true, | ||
990 | + "defaultColumnVisibility": "visible", | ||
991 | + "columnSelectionToDisplay": "enabled", | ||
992 | + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", | ||
993 | + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';" | ||
994 | + }, | ||
995 | + "_hash": 0.7764426948615217, | ||
996 | + "units": null, | ||
997 | + "decimals": null, | ||
998 | + "funcBody": null, | ||
999 | + "usePostProcessing": null, | ||
1000 | + "postFuncBody": null | ||
1001 | + }, | ||
1002 | + { | ||
1003 | + "name": "sw_checksum", | ||
1004 | + "type": "attribute", | ||
1005 | + "label": "sw_checksum", | ||
1006 | + "color": "#3f51b5", | ||
1007 | + "settings": { | ||
1008 | + "columnWidth": "0px", | ||
1009 | + "useCellStyleFunction": false, | ||
1010 | + "cellStyleFunction": "", | ||
1011 | + "useCellContentFunction": false, | ||
1012 | + "defaultColumnVisibility": "hidden", | ||
1013 | + "columnSelectionToDisplay": "disabled" | ||
1014 | + }, | ||
1015 | + "_hash": 0.5594087842471693, | ||
1016 | + "units": null, | ||
1017 | + "decimals": null, | ||
1018 | + "funcBody": null, | ||
1019 | + "usePostProcessing": null, | ||
1020 | + "postFuncBody": null | ||
1021 | + }, | ||
1022 | + { | ||
1023 | + "name": "sw_url", | ||
1024 | + "type": "attribute", | ||
1025 | + "label": "sw_url", | ||
1026 | + "color": "#e91e63", | ||
1027 | + "settings": { | ||
1028 | + "columnWidth": "0px", | ||
1029 | + "useCellStyleFunction": false, | ||
1030 | + "cellStyleFunction": "", | ||
1031 | + "useCellContentFunction": false, | ||
1032 | + "cellContentFunction": "", | ||
1033 | + "defaultColumnVisibility": "hidden", | ||
1034 | + "columnSelectionToDisplay": "disabled" | ||
1035 | + }, | ||
1036 | + "_hash": 0.3355829384124256, | ||
1037 | + "units": null, | ||
1038 | + "decimals": null, | ||
1039 | + "funcBody": null, | ||
1040 | + "usePostProcessing": null, | ||
1041 | + "postFuncBody": null | ||
1042 | + } | ||
1043 | + ] | ||
1044 | + } | ||
1045 | + ], | ||
1046 | + "actions": { | ||
1047 | + "actionCellButton": [ | ||
1048 | + { | ||
1049 | + "name": "History software update", | ||
1050 | + "icon": "history", | ||
1051 | + "type": "openDashboardState", | ||
1052 | + "targetDashboardStateId": "device_software_history", | ||
1053 | + "setEntityId": true, | ||
1054 | + "stateEntityParamName": null, | ||
1055 | + "openInSeparateDialog": false, | ||
1056 | + "dialogTitle": "", | ||
1057 | + "dialogHideDashboardToolbar": true, | ||
1058 | + "dialogWidth": null, | ||
1059 | + "dialogHeight": null, | ||
1060 | + "openRightLayout": false, | ||
1061 | + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" | ||
1062 | + }, | ||
1063 | + { | ||
1064 | + "name": "Edit software", | ||
1065 | + "icon": "edit", | ||
1066 | + "type": "customPretty", | ||
1067 | + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | ||
1068 | + "customCss": "form {\n min-width: 300px !important;\n}", | ||
1069 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | ||
1070 | + "customResources": [], | ||
1071 | + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | ||
1072 | + }, | ||
1073 | + { | ||
1074 | + "name": "Download software", | ||
1075 | + "icon": "file_download", | ||
1076 | + "type": "custom", | ||
1077 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1078 | + "id": "12533058-42f6-e75f-620c-219c48d01ec0" | ||
1079 | + }, | ||
1080 | + { | ||
1081 | + "name": "Copy checksum/URL", | ||
1082 | + "icon": "content_copy", | ||
1083 | + "type": "custom", | ||
1084 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1085 | + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | ||
1086 | + } | ||
1087 | + ] | ||
1088 | + }, | ||
1089 | + "showTitleIcon": false, | ||
1090 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
1091 | + "iconSize": "24px", | ||
1092 | + "titleTooltip": "", | ||
1093 | + "widgetStyle": {} | ||
1094 | + }, | ||
1095 | + "row": 0, | ||
1096 | + "col": 0, | ||
1097 | + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401" | ||
1098 | + }, | ||
1099 | + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { | ||
1100 | + "isSystemType": true, | ||
1101 | + "bundleAlias": "cards", | ||
1102 | + "typeAlias": "entities_table", | ||
1103 | + "type": "latest", | ||
1104 | + "title": "New widget", | ||
1105 | + "image": null, | ||
1106 | + "description": null, | ||
1107 | + "sizeX": 7.5, | ||
1108 | + "sizeY": 6.5, | ||
1109 | + "config": { | ||
1110 | + "timewindow": { | ||
1111 | + "realtime": { | ||
1112 | + "interval": 1000, | ||
1113 | + "timewindowMs": 86400000 | ||
1114 | + }, | ||
1115 | + "aggregation": { | ||
1116 | + "type": "NONE", | ||
1117 | + "limit": 200 | ||
1118 | + } | ||
1119 | + }, | ||
1120 | + "showTitle": true, | ||
1121 | + "backgroundColor": "rgb(255, 255, 255)", | ||
1122 | + "color": "rgba(0, 0, 0, 0.87)", | ||
1123 | + "padding": "4px", | ||
1124 | + "settings": { | ||
1125 | + "enableSearch": true, | ||
1126 | + "displayPagination": true, | ||
1127 | + "defaultPageSize": 10, | ||
1128 | + "defaultSortOrder": "entityLabel", | ||
1129 | + "displayEntityName": false, | ||
1130 | + "displayEntityType": false, | ||
1131 | + "enableSelectColumnDisplay": false, | ||
1132 | + "enableStickyHeader": true, | ||
1133 | + "enableStickyAction": true, | ||
1134 | + "entitiesTitle": "Devices", | ||
1135 | + "displayEntityLabel": true, | ||
1136 | + "entityLabelColumnTitle": "Device" | ||
1137 | + }, | ||
1138 | + "title": "New Entities table", | ||
1139 | + "dropShadow": true, | ||
1140 | + "enableFullscreen": true, | ||
1141 | + "titleStyle": { | ||
1142 | + "fontSize": "16px", | ||
1143 | + "fontWeight": 400, | ||
1144 | + "padding": "5px 10px 5px 10px" | ||
1145 | + }, | ||
1146 | + "useDashboardTimewindow": false, | ||
1147 | + "showLegend": false, | ||
1148 | + "datasources": [ | ||
1149 | + { | ||
1150 | + "type": "entity", | ||
1151 | + "name": null, | ||
1152 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
1153 | + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", | ||
1154 | + "dataKeys": [ | ||
1155 | + { | ||
1156 | + "name": "current_sw_title", | ||
1157 | + "type": "timeseries", | ||
1158 | + "label": "Current SW title", | ||
1159 | + "color": "#2196f3", | ||
1160 | + "settings": { | ||
1161 | + "columnWidth": "0px", | ||
1162 | + "useCellStyleFunction": false, | ||
1163 | + "cellStyleFunction": "", | ||
1164 | + "useCellContentFunction": false, | ||
1165 | + "defaultColumnVisibility": "visible", | ||
1166 | + "columnSelectionToDisplay": "enabled" | ||
1167 | + }, | ||
1168 | + "_hash": 0.09545533885166413, | ||
1169 | + "units": null, | ||
1170 | + "decimals": null, | ||
1171 | + "funcBody": null, | ||
1172 | + "usePostProcessing": null, | ||
1173 | + "postFuncBody": null | ||
1174 | + }, | ||
1175 | + { | ||
1176 | + "name": "current_sw_version", | ||
1177 | + "type": "timeseries", | ||
1178 | + "label": "Current SW version", | ||
1179 | + "color": "#4caf50", | ||
1180 | + "settings": { | ||
1181 | + "columnWidth": "0px", | ||
1182 | + "useCellStyleFunction": false, | ||
1183 | + "cellStyleFunction": "", | ||
1184 | + "useCellContentFunction": false, | ||
1185 | + "defaultColumnVisibility": "visible", | ||
1186 | + "columnSelectionToDisplay": "enabled" | ||
1187 | + }, | ||
1188 | + "_hash": 0.7206056602328659, | ||
1189 | + "units": null, | ||
1190 | + "decimals": null, | ||
1191 | + "funcBody": null, | ||
1192 | + "usePostProcessing": null, | ||
1193 | + "postFuncBody": null | ||
1194 | + }, | ||
1195 | + { | ||
1196 | + "name": "target_sw_title", | ||
1197 | + "type": "timeseries", | ||
1198 | + "label": "Target SW title", | ||
1199 | + "color": "#ffc107", | ||
1200 | + "settings": { | ||
1201 | + "columnWidth": "0px", | ||
1202 | + "useCellStyleFunction": false, | ||
1203 | + "cellStyleFunction": "", | ||
1204 | + "useCellContentFunction": false, | ||
1205 | + "defaultColumnVisibility": "visible", | ||
1206 | + "columnSelectionToDisplay": "enabled" | ||
1207 | + }, | ||
1208 | + "_hash": 0.9934225682766313, | ||
1209 | + "units": null, | ||
1210 | + "decimals": null, | ||
1211 | + "funcBody": null, | ||
1212 | + "usePostProcessing": null, | ||
1213 | + "postFuncBody": null | ||
1214 | + }, | ||
1215 | + { | ||
1216 | + "name": "target_sw_version", | ||
1217 | + "type": "timeseries", | ||
1218 | + "label": "Target SW version", | ||
1219 | + "color": "#607d8b", | ||
1220 | + "settings": { | ||
1221 | + "columnWidth": "0px", | ||
1222 | + "useCellStyleFunction": false, | ||
1223 | + "cellStyleFunction": "", | ||
1224 | + "useCellContentFunction": false, | ||
1225 | + "cellContentFunction": "", | ||
1226 | + "defaultColumnVisibility": "visible", | ||
1227 | + "columnSelectionToDisplay": "enabled" | ||
1228 | + }, | ||
1229 | + "_hash": 0.5251724416842531, | ||
1230 | + "units": null, | ||
1231 | + "decimals": null, | ||
1232 | + "funcBody": null, | ||
1233 | + "usePostProcessing": null, | ||
1234 | + "postFuncBody": null | ||
1235 | + }, | ||
1236 | + { | ||
1237 | + "name": "target_sw_ts", | ||
1238 | + "type": "timeseries", | ||
1239 | + "label": "Target SW set time", | ||
1240 | + "color": "#e91e63", | ||
1241 | + "settings": { | ||
1242 | + "columnWidth": "0px", | ||
1243 | + "useCellStyleFunction": false, | ||
1244 | + "cellStyleFunction": "", | ||
1245 | + "useCellContentFunction": true, | ||
1246 | + "defaultColumnVisibility": "visible", | ||
1247 | + "columnSelectionToDisplay": "enabled", | ||
1248 | + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" | ||
1249 | + }, | ||
1250 | + "_hash": 0.31823244858578237, | ||
1251 | + "units": null, | ||
1252 | + "decimals": null, | ||
1253 | + "funcBody": null, | ||
1254 | + "usePostProcessing": null, | ||
1255 | + "postFuncBody": null | ||
1256 | + }, | ||
1257 | + { | ||
1258 | + "name": "sw_state", | ||
1259 | + "type": "timeseries", | ||
1260 | + "label": "Progress", | ||
1261 | + "color": "#9c27b0", | ||
1262 | + "settings": { | ||
1263 | + "columnWidth": "30%", | ||
1264 | + "useCellStyleFunction": true, | ||
1265 | + "useCellContentFunction": true, | ||
1266 | + "defaultColumnVisibility": "visible", | ||
1267 | + "columnSelectionToDisplay": "enabled", | ||
1268 | + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", | ||
1269 | + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}" | ||
1270 | + }, | ||
1271 | + "_hash": 0.8174211757846257, | ||
1272 | + "units": null, | ||
1273 | + "decimals": null, | ||
1274 | + "funcBody": null, | ||
1275 | + "usePostProcessing": null, | ||
1276 | + "postFuncBody": null | ||
1277 | + }, | ||
1278 | + { | ||
1279 | + "name": "sw_state", | ||
1280 | + "type": "timeseries", | ||
1281 | + "label": "Status", | ||
1282 | + "color": "#f44336", | ||
1283 | + "settings": { | ||
1284 | + "columnWidth": "130px", | ||
1285 | + "useCellStyleFunction": true, | ||
1286 | + "useCellContentFunction": true, | ||
1287 | + "defaultColumnVisibility": "visible", | ||
1288 | + "columnSelectionToDisplay": "enabled", | ||
1289 | + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", | ||
1290 | + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';" | ||
1291 | + }, | ||
1292 | + "_hash": 0.7764426948615217, | ||
1293 | + "units": null, | ||
1294 | + "decimals": null, | ||
1295 | + "funcBody": null, | ||
1296 | + "usePostProcessing": null, | ||
1297 | + "postFuncBody": null | ||
1298 | + }, | ||
1299 | + { | ||
1300 | + "name": "sw_checksum", | ||
1301 | + "type": "attribute", | ||
1302 | + "label": "sw_checksum", | ||
1303 | + "color": "#3f51b5", | ||
1304 | + "settings": { | ||
1305 | + "columnWidth": "0px", | ||
1306 | + "useCellStyleFunction": false, | ||
1307 | + "cellStyleFunction": "", | ||
1308 | + "useCellContentFunction": false, | ||
1309 | + "defaultColumnVisibility": "hidden", | ||
1310 | + "columnSelectionToDisplay": "disabled" | ||
1311 | + }, | ||
1312 | + "_hash": 0.5594087842471693, | ||
1313 | + "units": null, | ||
1314 | + "decimals": null, | ||
1315 | + "funcBody": null, | ||
1316 | + "usePostProcessing": null, | ||
1317 | + "postFuncBody": null | ||
1318 | + }, | ||
1319 | + { | ||
1320 | + "name": "sw_url", | ||
1321 | + "type": "attribute", | ||
1322 | + "label": "sw_url", | ||
1323 | + "color": "#e91e63", | ||
1324 | + "settings": { | ||
1325 | + "columnWidth": "0px", | ||
1326 | + "useCellStyleFunction": false, | ||
1327 | + "cellStyleFunction": "", | ||
1328 | + "useCellContentFunction": false, | ||
1329 | + "cellContentFunction": "", | ||
1330 | + "defaultColumnVisibility": "hidden", | ||
1331 | + "columnSelectionToDisplay": "disabled" | ||
1332 | + }, | ||
1333 | + "_hash": 0.3355829384124256, | ||
1334 | + "units": null, | ||
1335 | + "decimals": null, | ||
1336 | + "funcBody": null, | ||
1337 | + "usePostProcessing": null, | ||
1338 | + "postFuncBody": null | ||
1339 | + } | ||
1340 | + ] | ||
1341 | + } | ||
1342 | + ], | ||
1343 | + "actions": { | ||
1344 | + "actionCellButton": [ | ||
1345 | + { | ||
1346 | + "name": "History software update", | ||
1347 | + "icon": "history", | ||
1348 | + "type": "openDashboardState", | ||
1349 | + "targetDashboardStateId": "device_software_history", | ||
1350 | + "setEntityId": true, | ||
1351 | + "stateEntityParamName": null, | ||
1352 | + "openInSeparateDialog": false, | ||
1353 | + "dialogTitle": "", | ||
1354 | + "dialogHideDashboardToolbar": true, | ||
1355 | + "dialogWidth": null, | ||
1356 | + "dialogHeight": null, | ||
1357 | + "openRightLayout": false, | ||
1358 | + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" | ||
1359 | + }, | ||
1360 | + { | ||
1361 | + "name": "Edit software", | ||
1362 | + "icon": "edit", | ||
1363 | + "type": "customPretty", | ||
1364 | + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | ||
1365 | + "customCss": "form {\n min-width: 300px !important;\n}", | ||
1366 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | ||
1367 | + "customResources": [], | ||
1368 | + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | ||
1369 | + }, | ||
1370 | + { | ||
1371 | + "name": "Download software", | ||
1372 | + "icon": "file_download", | ||
1373 | + "type": "custom", | ||
1374 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1375 | + "id": "12533058-42f6-e75f-620c-219c48d01ec0" | ||
1376 | + }, | ||
1377 | + { | ||
1378 | + "name": "Copy checksum/URL", | ||
1379 | + "icon": "content_copy", | ||
1380 | + "type": "custom", | ||
1381 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1382 | + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | ||
1383 | + } | ||
1384 | + ] | ||
1385 | + }, | ||
1386 | + "showTitleIcon": false, | ||
1387 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
1388 | + "iconSize": "24px", | ||
1389 | + "titleTooltip": "", | ||
1390 | + "widgetStyle": {} | ||
1391 | + }, | ||
1392 | + "row": 0, | ||
1393 | + "col": 0, | ||
1394 | + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" | ||
1395 | + }, | ||
1396 | + "3624013b-378c-f110-5eba-ae95c25a4dcc": { | ||
1397 | + "isSystemType": true, | ||
1398 | + "bundleAlias": "cards", | ||
1399 | + "typeAlias": "entities_table", | ||
1400 | + "type": "latest", | ||
1401 | + "title": "New widget", | ||
1402 | + "image": null, | ||
1403 | + "description": null, | ||
1404 | + "sizeX": 7.5, | ||
1405 | + "sizeY": 6.5, | ||
1406 | + "config": { | ||
1407 | + "timewindow": { | ||
1408 | + "realtime": { | ||
1409 | + "interval": 1000, | ||
1410 | + "timewindowMs": 86400000 | ||
1411 | + }, | ||
1412 | + "aggregation": { | ||
1413 | + "type": "NONE", | ||
1414 | + "limit": 200 | ||
1415 | + } | ||
1416 | + }, | ||
1417 | + "showTitle": true, | ||
1418 | + "backgroundColor": "rgb(255, 255, 255)", | ||
1419 | + "color": "rgba(0, 0, 0, 0.87)", | ||
1420 | + "padding": "4px", | ||
1421 | + "settings": { | ||
1422 | + "enableSearch": true, | ||
1423 | + "displayPagination": true, | ||
1424 | + "defaultPageSize": 10, | ||
1425 | + "defaultSortOrder": "entityLabel", | ||
1426 | + "displayEntityName": false, | ||
1427 | + "displayEntityType": false, | ||
1428 | + "enableSelectColumnDisplay": false, | ||
1429 | + "enableStickyHeader": true, | ||
1430 | + "enableStickyAction": true, | ||
1431 | + "entitiesTitle": "Devices", | ||
1432 | + "displayEntityLabel": true, | ||
1433 | + "entityLabelColumnTitle": "Device" | ||
1434 | + }, | ||
1435 | + "title": "New Entities table", | ||
1436 | + "dropShadow": true, | ||
1437 | + "enableFullscreen": true, | ||
1438 | + "titleStyle": { | ||
1439 | + "fontSize": "16px", | ||
1440 | + "fontWeight": 400, | ||
1441 | + "padding": "5px 10px 5px 10px" | ||
1442 | + }, | ||
1443 | + "useDashboardTimewindow": false, | ||
1444 | + "showLegend": false, | ||
1445 | + "datasources": [ | ||
1446 | + { | ||
1447 | + "type": "entity", | ||
1448 | + "name": null, | ||
1449 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
1450 | + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", | ||
1451 | + "dataKeys": [ | ||
1452 | + { | ||
1453 | + "name": "current_sw_title", | ||
1454 | + "type": "timeseries", | ||
1455 | + "label": "Current SW title", | ||
1456 | + "color": "#2196f3", | ||
1457 | + "settings": { | ||
1458 | + "columnWidth": "0px", | ||
1459 | + "useCellStyleFunction": false, | ||
1460 | + "cellStyleFunction": "", | ||
1461 | + "useCellContentFunction": false, | ||
1462 | + "defaultColumnVisibility": "visible", | ||
1463 | + "columnSelectionToDisplay": "enabled" | ||
1464 | + }, | ||
1465 | + "_hash": 0.09545533885166413, | ||
1466 | + "units": null, | ||
1467 | + "decimals": null, | ||
1468 | + "funcBody": null, | ||
1469 | + "usePostProcessing": null, | ||
1470 | + "postFuncBody": null | ||
1471 | + }, | ||
1472 | + { | ||
1473 | + "name": "current_sw_version", | ||
1474 | + "type": "timeseries", | ||
1475 | + "label": "Current SW version", | ||
1476 | + "color": "#4caf50", | ||
1477 | + "settings": { | ||
1478 | + "columnWidth": "0px", | ||
1479 | + "useCellStyleFunction": false, | ||
1480 | + "cellStyleFunction": "", | ||
1481 | + "useCellContentFunction": false, | ||
1482 | + "defaultColumnVisibility": "visible", | ||
1483 | + "columnSelectionToDisplay": "enabled" | ||
1484 | + }, | ||
1485 | + "_hash": 0.7206056602328659, | ||
1486 | + "units": null, | ||
1487 | + "decimals": null, | ||
1488 | + "funcBody": null, | ||
1489 | + "usePostProcessing": null, | ||
1490 | + "postFuncBody": null | ||
1491 | + }, | ||
1492 | + { | ||
1493 | + "name": "target_sw_title", | ||
1494 | + "type": "timeseries", | ||
1495 | + "label": "Target SW title", | ||
1496 | + "color": "#ffc107", | ||
1497 | + "settings": { | ||
1498 | + "columnWidth": "0px", | ||
1499 | + "useCellStyleFunction": false, | ||
1500 | + "cellStyleFunction": "", | ||
1501 | + "useCellContentFunction": false, | ||
1502 | + "defaultColumnVisibility": "visible", | ||
1503 | + "columnSelectionToDisplay": "enabled" | ||
1504 | + }, | ||
1505 | + "_hash": 0.9934225682766313, | ||
1506 | + "units": null, | ||
1507 | + "decimals": null, | ||
1508 | + "funcBody": null, | ||
1509 | + "usePostProcessing": null, | ||
1510 | + "postFuncBody": null | ||
1511 | + }, | ||
1512 | + { | ||
1513 | + "name": "target_sw_version", | ||
1514 | + "type": "timeseries", | ||
1515 | + "label": "Target SW version", | ||
1516 | + "color": "#607d8b", | ||
1517 | + "settings": { | ||
1518 | + "columnWidth": "0px", | ||
1519 | + "useCellStyleFunction": false, | ||
1520 | + "cellStyleFunction": "", | ||
1521 | + "useCellContentFunction": false, | ||
1522 | + "cellContentFunction": "", | ||
1523 | + "defaultColumnVisibility": "visible", | ||
1524 | + "columnSelectionToDisplay": "enabled" | ||
1525 | + }, | ||
1526 | + "_hash": 0.5251724416842531, | ||
1527 | + "units": null, | ||
1528 | + "decimals": null, | ||
1529 | + "funcBody": null, | ||
1530 | + "usePostProcessing": null, | ||
1531 | + "postFuncBody": null | ||
1532 | + }, | ||
1533 | + { | ||
1534 | + "name": "target_sw_ts", | ||
1535 | + "type": "timeseries", | ||
1536 | + "label": "Target SW set time", | ||
1537 | + "color": "#e91e63", | ||
1538 | + "settings": { | ||
1539 | + "columnWidth": "0px", | ||
1540 | + "useCellStyleFunction": false, | ||
1541 | + "cellStyleFunction": "", | ||
1542 | + "useCellContentFunction": true, | ||
1543 | + "defaultColumnVisibility": "visible", | ||
1544 | + "columnSelectionToDisplay": "enabled", | ||
1545 | + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" | ||
1546 | + }, | ||
1547 | + "_hash": 0.31823244858578237, | ||
1548 | + "units": null, | ||
1549 | + "decimals": null, | ||
1550 | + "funcBody": null, | ||
1551 | + "usePostProcessing": null, | ||
1552 | + "postFuncBody": null | ||
1553 | + }, | ||
1554 | + { | ||
1555 | + "name": "sw_state", | ||
1556 | + "type": "timeseries", | ||
1557 | + "label": "Progress", | ||
1558 | + "color": "#9c27b0", | ||
1559 | + "settings": { | ||
1560 | + "columnWidth": "30%", | ||
1561 | + "useCellStyleFunction": true, | ||
1562 | + "useCellContentFunction": true, | ||
1563 | + "defaultColumnVisibility": "visible", | ||
1564 | + "columnSelectionToDisplay": "enabled", | ||
1565 | + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", | ||
1566 | + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}" | ||
1567 | + }, | ||
1568 | + "_hash": 0.8174211757846257, | ||
1569 | + "units": null, | ||
1570 | + "decimals": null, | ||
1571 | + "funcBody": null, | ||
1572 | + "usePostProcessing": null, | ||
1573 | + "postFuncBody": null | ||
1574 | + }, | ||
1575 | + { | ||
1576 | + "name": "sw_state", | ||
1577 | + "type": "timeseries", | ||
1578 | + "label": "Status", | ||
1579 | + "color": "#f44336", | ||
1580 | + "settings": { | ||
1581 | + "columnWidth": "130px", | ||
1582 | + "useCellStyleFunction": true, | ||
1583 | + "useCellContentFunction": true, | ||
1584 | + "defaultColumnVisibility": "visible", | ||
1585 | + "columnSelectionToDisplay": "enabled", | ||
1586 | + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", | ||
1587 | + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';" | ||
1588 | + }, | ||
1589 | + "_hash": 0.7764426948615217, | ||
1590 | + "units": null, | ||
1591 | + "decimals": null, | ||
1592 | + "funcBody": null, | ||
1593 | + "usePostProcessing": null, | ||
1594 | + "postFuncBody": null | ||
1595 | + }, | ||
1596 | + { | ||
1597 | + "name": "sw_checksum", | ||
1598 | + "type": "attribute", | ||
1599 | + "label": "sw_checksum", | ||
1600 | + "color": "#3f51b5", | ||
1601 | + "settings": { | ||
1602 | + "columnWidth": "0px", | ||
1603 | + "useCellStyleFunction": false, | ||
1604 | + "cellStyleFunction": "", | ||
1605 | + "useCellContentFunction": false, | ||
1606 | + "defaultColumnVisibility": "hidden", | ||
1607 | + "columnSelectionToDisplay": "disabled" | ||
1608 | + }, | ||
1609 | + "_hash": 0.5594087842471693, | ||
1610 | + "units": null, | ||
1611 | + "decimals": null, | ||
1612 | + "funcBody": null, | ||
1613 | + "usePostProcessing": null, | ||
1614 | + "postFuncBody": null | ||
1615 | + }, | ||
1616 | + { | ||
1617 | + "name": "sw_url", | ||
1618 | + "type": "attribute", | ||
1619 | + "label": "sw_url", | ||
1620 | + "color": "#e91e63", | ||
1621 | + "settings": { | ||
1622 | + "columnWidth": "0px", | ||
1623 | + "useCellStyleFunction": false, | ||
1624 | + "cellStyleFunction": "", | ||
1625 | + "useCellContentFunction": false, | ||
1626 | + "cellContentFunction": "", | ||
1627 | + "defaultColumnVisibility": "hidden", | ||
1628 | + "columnSelectionToDisplay": "disabled" | ||
1629 | + }, | ||
1630 | + "_hash": 0.3355829384124256, | ||
1631 | + "units": null, | ||
1632 | + "decimals": null, | ||
1633 | + "funcBody": null, | ||
1634 | + "usePostProcessing": null, | ||
1635 | + "postFuncBody": null | ||
1636 | + } | ||
1637 | + ] | ||
1638 | + } | ||
1639 | + ], | ||
1640 | + "actions": { | ||
1641 | + "actionCellButton": [ | ||
1642 | + { | ||
1643 | + "name": "History software update", | ||
1644 | + "icon": "history", | ||
1645 | + "type": "openDashboardState", | ||
1646 | + "targetDashboardStateId": "device_software_history", | ||
1647 | + "setEntityId": true, | ||
1648 | + "stateEntityParamName": null, | ||
1649 | + "openInSeparateDialog": false, | ||
1650 | + "dialogTitle": "", | ||
1651 | + "dialogHideDashboardToolbar": true, | ||
1652 | + "dialogWidth": null, | ||
1653 | + "dialogHeight": null, | ||
1654 | + "openRightLayout": false, | ||
1655 | + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" | ||
1656 | + }, | ||
1657 | + { | ||
1658 | + "name": "Edit software", | ||
1659 | + "icon": "edit", | ||
1660 | + "type": "customPretty", | ||
1661 | + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | ||
1662 | + "customCss": "form {\n min-width: 300px !important;\n}", | ||
1663 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | ||
1664 | + "customResources": [], | ||
1665 | + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | ||
1666 | + }, | ||
1667 | + { | ||
1668 | + "name": "Download software", | ||
1669 | + "icon": "file_download", | ||
1670 | + "type": "custom", | ||
1671 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1672 | + "id": "12533058-42f6-e75f-620c-219c48d01ec0" | ||
1673 | + }, | ||
1674 | + { | ||
1675 | + "name": "Copy checksum/URL", | ||
1676 | + "icon": "content_copy", | ||
1677 | + "type": "custom", | ||
1678 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1679 | + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | ||
1680 | + } | ||
1681 | + ] | ||
1682 | + }, | ||
1683 | + "showTitleIcon": false, | ||
1684 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
1685 | + "iconSize": "24px", | ||
1686 | + "titleTooltip": "", | ||
1687 | + "widgetStyle": {} | ||
1688 | + }, | ||
1689 | + "row": 0, | ||
1690 | + "col": 0, | ||
1691 | + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc" | ||
1692 | + }, | ||
1693 | + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { | ||
1694 | + "isSystemType": true, | ||
1695 | + "bundleAlias": "cards", | ||
1696 | + "typeAlias": "entities_table", | ||
1697 | + "type": "latest", | ||
1698 | + "title": "New widget", | ||
1699 | + "image": null, | ||
1700 | + "description": null, | ||
1701 | + "sizeX": 7.5, | ||
1702 | + "sizeY": 6.5, | ||
1703 | + "config": { | ||
1704 | + "timewindow": { | ||
1705 | + "realtime": { | ||
1706 | + "interval": 1000, | ||
1707 | + "timewindowMs": 86400000 | ||
1708 | + }, | ||
1709 | + "aggregation": { | ||
1710 | + "type": "NONE", | ||
1711 | + "limit": 200 | ||
1712 | + } | ||
1713 | + }, | ||
1714 | + "showTitle": true, | ||
1715 | + "backgroundColor": "rgb(255, 255, 255)", | ||
1716 | + "color": "rgba(0, 0, 0, 0.87)", | ||
1717 | + "padding": "4px", | ||
1718 | + "settings": { | ||
1719 | + "enableSearch": true, | ||
1720 | + "displayPagination": true, | ||
1721 | + "defaultPageSize": 10, | ||
1722 | + "defaultSortOrder": "entityLabel", | ||
1723 | + "displayEntityName": false, | ||
1724 | + "displayEntityType": false, | ||
1725 | + "enableSelectColumnDisplay": false, | ||
1726 | + "enableStickyHeader": true, | ||
1727 | + "enableStickyAction": true, | ||
1728 | + "entitiesTitle": "Devices", | ||
1729 | + "displayEntityLabel": true, | ||
1730 | + "entityLabelColumnTitle": "Device" | ||
1731 | + }, | ||
1732 | + "title": "New Entities table", | ||
1733 | + "dropShadow": true, | ||
1734 | + "enableFullscreen": true, | ||
1735 | + "titleStyle": { | ||
1736 | + "fontSize": "16px", | ||
1737 | + "fontWeight": 400, | ||
1738 | + "padding": "5px 10px 5px 10px" | ||
1739 | + }, | ||
1740 | + "useDashboardTimewindow": false, | ||
1741 | + "showLegend": false, | ||
1742 | + "datasources": [ | ||
1743 | + { | ||
1744 | + "type": "entity", | ||
1745 | + "name": null, | ||
1746 | + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
1747 | + "filterId": "6044e198-df64-cd76-f339-696f220c4943", | ||
1748 | + "dataKeys": [ | ||
1749 | + { | ||
1750 | + "name": "current_sw_title", | ||
1751 | + "type": "timeseries", | ||
1752 | + "label": "Current SW title", | ||
1753 | + "color": "#2196f3", | ||
1754 | + "settings": { | ||
1755 | + "columnWidth": "0px", | ||
1756 | + "useCellStyleFunction": false, | ||
1757 | + "cellStyleFunction": "", | ||
1758 | + "useCellContentFunction": false, | ||
1759 | + "defaultColumnVisibility": "visible", | ||
1760 | + "columnSelectionToDisplay": "enabled" | ||
1761 | + }, | ||
1762 | + "_hash": 0.09545533885166413, | ||
1763 | + "units": null, | ||
1764 | + "decimals": null, | ||
1765 | + "funcBody": null, | ||
1766 | + "usePostProcessing": null, | ||
1767 | + "postFuncBody": null | ||
1768 | + }, | ||
1769 | + { | ||
1770 | + "name": "current_sw_version", | ||
1771 | + "type": "timeseries", | ||
1772 | + "label": "Current SW version", | ||
1773 | + "color": "#4caf50", | ||
1774 | + "settings": { | ||
1775 | + "columnWidth": "0px", | ||
1776 | + "useCellStyleFunction": false, | ||
1777 | + "cellStyleFunction": "", | ||
1778 | + "useCellContentFunction": false, | ||
1779 | + "defaultColumnVisibility": "visible", | ||
1780 | + "columnSelectionToDisplay": "enabled" | ||
1781 | + }, | ||
1782 | + "_hash": 0.7206056602328659, | ||
1783 | + "units": null, | ||
1784 | + "decimals": null, | ||
1785 | + "funcBody": null, | ||
1786 | + "usePostProcessing": null, | ||
1787 | + "postFuncBody": null | ||
1788 | + }, | ||
1789 | + { | ||
1790 | + "name": "target_sw_title", | ||
1791 | + "type": "timeseries", | ||
1792 | + "label": "Target SW title", | ||
1793 | + "color": "#ffc107", | ||
1794 | + "settings": { | ||
1795 | + "columnWidth": "0px", | ||
1796 | + "useCellStyleFunction": false, | ||
1797 | + "cellStyleFunction": "", | ||
1798 | + "useCellContentFunction": false, | ||
1799 | + "defaultColumnVisibility": "visible", | ||
1800 | + "columnSelectionToDisplay": "enabled" | ||
1801 | + }, | ||
1802 | + "_hash": 0.9934225682766313, | ||
1803 | + "units": null, | ||
1804 | + "decimals": null, | ||
1805 | + "funcBody": null, | ||
1806 | + "usePostProcessing": null, | ||
1807 | + "postFuncBody": null | ||
1808 | + }, | ||
1809 | + { | ||
1810 | + "name": "target_sw_version", | ||
1811 | + "type": "timeseries", | ||
1812 | + "label": "Target SW version", | ||
1813 | + "color": "#607d8b", | ||
1814 | + "settings": { | ||
1815 | + "columnWidth": "0px", | ||
1816 | + "useCellStyleFunction": false, | ||
1817 | + "cellStyleFunction": "", | ||
1818 | + "useCellContentFunction": false, | ||
1819 | + "cellContentFunction": "", | ||
1820 | + "defaultColumnVisibility": "visible", | ||
1821 | + "columnSelectionToDisplay": "enabled" | ||
1822 | + }, | ||
1823 | + "_hash": 0.5251724416842531, | ||
1824 | + "units": null, | ||
1825 | + "decimals": null, | ||
1826 | + "funcBody": null, | ||
1827 | + "usePostProcessing": null, | ||
1828 | + "postFuncBody": null | ||
1829 | + }, | ||
1830 | + { | ||
1831 | + "name": "target_sw_ts", | ||
1832 | + "type": "timeseries", | ||
1833 | + "label": "Target SW set time", | ||
1834 | + "color": "#e91e63", | ||
1835 | + "settings": { | ||
1836 | + "columnWidth": "0px", | ||
1837 | + "useCellStyleFunction": false, | ||
1838 | + "cellStyleFunction": "", | ||
1839 | + "useCellContentFunction": true, | ||
1840 | + "defaultColumnVisibility": "visible", | ||
1841 | + "columnSelectionToDisplay": "enabled", | ||
1842 | + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" | ||
1843 | + }, | ||
1844 | + "_hash": 0.31823244858578237, | ||
1845 | + "units": null, | ||
1846 | + "decimals": null, | ||
1847 | + "funcBody": null, | ||
1848 | + "usePostProcessing": null, | ||
1849 | + "postFuncBody": null | ||
1850 | + }, | ||
1851 | + { | ||
1852 | + "name": "sw_state", | ||
1853 | + "type": "timeseries", | ||
1854 | + "label": "Progress", | ||
1855 | + "color": "#9c27b0", | ||
1856 | + "settings": { | ||
1857 | + "columnWidth": "30%", | ||
1858 | + "useCellStyleFunction": true, | ||
1859 | + "useCellContentFunction": true, | ||
1860 | + "defaultColumnVisibility": "visible", | ||
1861 | + "columnSelectionToDisplay": "enabled", | ||
1862 | + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", | ||
1863 | + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}" | ||
1864 | + }, | ||
1865 | + "_hash": 0.8174211757846257, | ||
1866 | + "units": null, | ||
1867 | + "decimals": null, | ||
1868 | + "funcBody": null, | ||
1869 | + "usePostProcessing": null, | ||
1870 | + "postFuncBody": null | ||
1871 | + }, | ||
1872 | + { | ||
1873 | + "name": "sw_state", | ||
1874 | + "type": "timeseries", | ||
1875 | + "label": "Status", | ||
1876 | + "color": "#f44336", | ||
1877 | + "settings": { | ||
1878 | + "columnWidth": "130px", | ||
1879 | + "useCellStyleFunction": true, | ||
1880 | + "useCellContentFunction": true, | ||
1881 | + "defaultColumnVisibility": "visible", | ||
1882 | + "columnSelectionToDisplay": "enabled", | ||
1883 | + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", | ||
1884 | + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';" | ||
1885 | + }, | ||
1886 | + "_hash": 0.7764426948615217, | ||
1887 | + "units": null, | ||
1888 | + "decimals": null, | ||
1889 | + "funcBody": null, | ||
1890 | + "usePostProcessing": null, | ||
1891 | + "postFuncBody": null | ||
1892 | + }, | ||
1893 | + { | ||
1894 | + "name": "sw_checksum", | ||
1895 | + "type": "attribute", | ||
1896 | + "label": "sw_checksum", | ||
1897 | + "color": "#3f51b5", | ||
1898 | + "settings": { | ||
1899 | + "columnWidth": "0px", | ||
1900 | + "useCellStyleFunction": false, | ||
1901 | + "cellStyleFunction": "", | ||
1902 | + "useCellContentFunction": false, | ||
1903 | + "defaultColumnVisibility": "hidden", | ||
1904 | + "columnSelectionToDisplay": "disabled" | ||
1905 | + }, | ||
1906 | + "_hash": 0.5594087842471693, | ||
1907 | + "units": null, | ||
1908 | + "decimals": null, | ||
1909 | + "funcBody": null, | ||
1910 | + "usePostProcessing": null, | ||
1911 | + "postFuncBody": null | ||
1912 | + }, | ||
1913 | + { | ||
1914 | + "name": "sw_url", | ||
1915 | + "type": "attribute", | ||
1916 | + "label": "sw_url", | ||
1917 | + "color": "#e91e63", | ||
1918 | + "settings": { | ||
1919 | + "columnWidth": "0px", | ||
1920 | + "useCellStyleFunction": false, | ||
1921 | + "cellStyleFunction": "", | ||
1922 | + "useCellContentFunction": false, | ||
1923 | + "cellContentFunction": "", | ||
1924 | + "defaultColumnVisibility": "hidden", | ||
1925 | + "columnSelectionToDisplay": "disabled" | ||
1926 | + }, | ||
1927 | + "_hash": 0.3355829384124256, | ||
1928 | + "units": null, | ||
1929 | + "decimals": null, | ||
1930 | + "funcBody": null, | ||
1931 | + "usePostProcessing": null, | ||
1932 | + "postFuncBody": null | ||
1933 | + } | ||
1934 | + ] | ||
1935 | + } | ||
1936 | + ], | ||
1937 | + "actions": { | ||
1938 | + "actionCellButton": [ | ||
1939 | + { | ||
1940 | + "name": "History software update", | ||
1941 | + "icon": "history", | ||
1942 | + "type": "openDashboardState", | ||
1943 | + "targetDashboardStateId": "device_software_history", | ||
1944 | + "setEntityId": true, | ||
1945 | + "stateEntityParamName": null, | ||
1946 | + "openInSeparateDialog": false, | ||
1947 | + "dialogTitle": "", | ||
1948 | + "dialogHideDashboardToolbar": true, | ||
1949 | + "dialogWidth": null, | ||
1950 | + "dialogHeight": null, | ||
1951 | + "openRightLayout": false, | ||
1952 | + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" | ||
1953 | + }, | ||
1954 | + { | ||
1955 | + "name": "Edit software", | ||
1956 | + "icon": "edit", | ||
1957 | + "type": "customPretty", | ||
1958 | + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit software {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n type=\"SOFTWARE\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"softwareId\">\n </tb-ota-package-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>", | ||
1959 | + "customCss": "form {\n min-width: 300px !important;\n}", | ||
1960 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", | ||
1961 | + "customResources": [], | ||
1962 | + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" | ||
1963 | + }, | ||
1964 | + { | ||
1965 | + "name": "Download software", | ||
1966 | + "icon": "file_download", | ||
1967 | + "type": "custom", | ||
1968 | + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1969 | + "id": "12533058-42f6-e75f-620c-219c48d01ec0" | ||
1970 | + }, | ||
1971 | + { | ||
1972 | + "name": "Copy checksum/URL", | ||
1973 | + "icon": "content_copy", | ||
1974 | + "type": "custom", | ||
1975 | + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", | ||
1976 | + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" | ||
1977 | + } | ||
1978 | + ] | ||
1979 | + }, | ||
1980 | + "showTitleIcon": false, | ||
1981 | + "iconColor": "rgba(0, 0, 0, 0.87)", | ||
1982 | + "iconSize": "24px", | ||
1983 | + "titleTooltip": "", | ||
1984 | + "widgetStyle": {} | ||
1985 | + }, | ||
1986 | + "row": 0, | ||
1987 | + "col": 0, | ||
1988 | + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" | ||
1989 | + } | ||
1990 | + }, | ||
1991 | + "states": { | ||
1992 | + "default": { | ||
1993 | + "name": "Device list", | ||
1994 | + "root": true, | ||
1995 | + "layouts": { | ||
1996 | + "main": { | ||
1997 | + "widgets": { | ||
1998 | + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { | ||
1999 | + "sizeX": 19, | ||
2000 | + "sizeY": 12, | ||
2001 | + "row": 0, | ||
2002 | + "col": 0 | ||
2003 | + }, | ||
2004 | + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { | ||
2005 | + "sizeX": 5, | ||
2006 | + "sizeY": 3, | ||
2007 | + "row": 0, | ||
2008 | + "col": 19 | ||
2009 | + }, | ||
2010 | + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { | ||
2011 | + "sizeX": 5, | ||
2012 | + "sizeY": 3, | ||
2013 | + "row": 3, | ||
2014 | + "col": 19 | ||
2015 | + }, | ||
2016 | + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { | ||
2017 | + "sizeX": 5, | ||
2018 | + "sizeY": 3, | ||
2019 | + "row": 9, | ||
2020 | + "col": 19 | ||
2021 | + }, | ||
2022 | + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { | ||
2023 | + "sizeX": 5, | ||
2024 | + "sizeY": 3, | ||
2025 | + "row": 6, | ||
2026 | + "col": 19 | ||
2027 | + } | ||
2028 | + }, | ||
2029 | + "gridSettings": { | ||
2030 | + "backgroundColor": "#eeeeee", | ||
2031 | + "color": "rgba(0,0,0,0.870588)", | ||
2032 | + "columns": 24, | ||
2033 | + "margin": 12, | ||
2034 | + "backgroundSizeMode": "100%", | ||
2035 | + "autoFillHeight": true, | ||
2036 | + "backgroundImageUrl": null, | ||
2037 | + "mobileAutoFillHeight": true, | ||
2038 | + "mobileRowHeight": 70 | ||
2039 | + } | ||
2040 | + } | ||
2041 | + } | ||
2042 | + }, | ||
2043 | + "device_software_history": { | ||
2044 | + "name": "Software history: ${entityName}", | ||
2045 | + "root": false, | ||
2046 | + "layouts": { | ||
2047 | + "main": { | ||
2048 | + "widgets": { | ||
2049 | + "100b756c-0082-6505-3ae1-3603e6deea48": { | ||
2050 | + "sizeX": 24, | ||
2051 | + "sizeY": 12, | ||
2052 | + "row": 0, | ||
2053 | + "col": 0 | ||
2054 | + } | ||
2055 | + }, | ||
2056 | + "gridSettings": { | ||
2057 | + "backgroundColor": "#eeeeee", | ||
2058 | + "color": "rgba(0,0,0,0.870588)", | ||
2059 | + "columns": 24, | ||
2060 | + "margin": 10, | ||
2061 | + "backgroundSizeMode": "100%", | ||
2062 | + "autoFillHeight": true, | ||
2063 | + "backgroundImageUrl": null, | ||
2064 | + "mobileAutoFillHeight": false, | ||
2065 | + "mobileRowHeight": 70 | ||
2066 | + } | ||
2067 | + } | ||
2068 | + } | ||
2069 | + }, | ||
2070 | + "device_waiting": { | ||
2071 | + "name": "Device waiting", | ||
2072 | + "root": false, | ||
2073 | + "layouts": { | ||
2074 | + "main": { | ||
2075 | + "widgets": { | ||
2076 | + "21be08bb-ec90-f760-ad6f-e7678f12c401": { | ||
2077 | + "sizeX": 24, | ||
2078 | + "sizeY": 12, | ||
2079 | + "row": 0, | ||
2080 | + "col": 0 | ||
2081 | + } | ||
2082 | + }, | ||
2083 | + "gridSettings": { | ||
2084 | + "backgroundColor": "#eeeeee", | ||
2085 | + "color": "rgba(0,0,0,0.870588)", | ||
2086 | + "columns": 24, | ||
2087 | + "margin": 10, | ||
2088 | + "backgroundSizeMode": "100%", | ||
2089 | + "autoFillHeight": true, | ||
2090 | + "backgroundImageUrl": null, | ||
2091 | + "mobileAutoFillHeight": false, | ||
2092 | + "mobileRowHeight": 70 | ||
2093 | + } | ||
2094 | + } | ||
2095 | + } | ||
2096 | + }, | ||
2097 | + "device_updating": { | ||
2098 | + "name": "Device updating", | ||
2099 | + "root": false, | ||
2100 | + "layouts": { | ||
2101 | + "main": { | ||
2102 | + "widgets": { | ||
2103 | + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { | ||
2104 | + "sizeX": 24, | ||
2105 | + "sizeY": 12, | ||
2106 | + "row": 0, | ||
2107 | + "col": 0 | ||
2108 | + } | ||
2109 | + }, | ||
2110 | + "gridSettings": { | ||
2111 | + "backgroundColor": "#eeeeee", | ||
2112 | + "color": "rgba(0,0,0,0.870588)", | ||
2113 | + "columns": 24, | ||
2114 | + "margin": 10, | ||
2115 | + "backgroundSizeMode": "100%", | ||
2116 | + "autoFillHeight": true, | ||
2117 | + "backgroundImageUrl": null, | ||
2118 | + "mobileAutoFillHeight": false, | ||
2119 | + "mobileRowHeight": 70 | ||
2120 | + } | ||
2121 | + } | ||
2122 | + } | ||
2123 | + }, | ||
2124 | + "device_updated": { | ||
2125 | + "name": "Device updated", | ||
2126 | + "root": false, | ||
2127 | + "layouts": { | ||
2128 | + "main": { | ||
2129 | + "widgets": { | ||
2130 | + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { | ||
2131 | + "sizeX": 27, | ||
2132 | + "sizeY": 12, | ||
2133 | + "row": 0, | ||
2134 | + "col": 0 | ||
2135 | + } | ||
2136 | + }, | ||
2137 | + "gridSettings": { | ||
2138 | + "backgroundColor": "#eeeeee", | ||
2139 | + "color": "rgba(0,0,0,0.870588)", | ||
2140 | + "columns": 24, | ||
2141 | + "margin": 10, | ||
2142 | + "backgroundSizeMode": "100%", | ||
2143 | + "autoFillHeight": true, | ||
2144 | + "backgroundImageUrl": null, | ||
2145 | + "mobileAutoFillHeight": false, | ||
2146 | + "mobileRowHeight": 70 | ||
2147 | + } | ||
2148 | + } | ||
2149 | + } | ||
2150 | + }, | ||
2151 | + "device_error": { | ||
2152 | + "name": "Device failed", | ||
2153 | + "root": false, | ||
2154 | + "layouts": { | ||
2155 | + "main": { | ||
2156 | + "widgets": { | ||
2157 | + "3624013b-378c-f110-5eba-ae95c25a4dcc": { | ||
2158 | + "sizeX": 24, | ||
2159 | + "sizeY": 12, | ||
2160 | + "row": 0, | ||
2161 | + "col": 0 | ||
2162 | + } | ||
2163 | + }, | ||
2164 | + "gridSettings": { | ||
2165 | + "backgroundColor": "#eeeeee", | ||
2166 | + "color": "rgba(0,0,0,0.870588)", | ||
2167 | + "columns": 24, | ||
2168 | + "margin": 10, | ||
2169 | + "backgroundSizeMode": "100%", | ||
2170 | + "autoFillHeight": true, | ||
2171 | + "backgroundImageUrl": null, | ||
2172 | + "mobileAutoFillHeight": false, | ||
2173 | + "mobileRowHeight": 70 | ||
2174 | + } | ||
2175 | + } | ||
2176 | + } | ||
2177 | + } | ||
2178 | + }, | ||
2179 | + "entityAliases": { | ||
2180 | + "639da5b4-31f0-0151-6282-c37a3897b7e8": { | ||
2181 | + "id": "639da5b4-31f0-0151-6282-c37a3897b7e8", | ||
2182 | + "alias": "All devices", | ||
2183 | + "filter": { | ||
2184 | + "type": "entityType", | ||
2185 | + "resolveMultiple": true, | ||
2186 | + "entityType": "DEVICE" | ||
2187 | + } | ||
2188 | + }, | ||
2189 | + "19f41c21-d9af-e666-8f50-e1748778f955": { | ||
2190 | + "id": "19f41c21-d9af-e666-8f50-e1748778f955", | ||
2191 | + "alias": "State entity", | ||
2192 | + "filter": { | ||
2193 | + "type": "stateEntity", | ||
2194 | + "resolveMultiple": false, | ||
2195 | + "stateEntityParamName": null, | ||
2196 | + "defaultStateEntity": null | ||
2197 | + } | ||
2198 | + } | ||
2199 | + }, | ||
2200 | + "filters": { | ||
2201 | + "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e": { | ||
2202 | + "id": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", | ||
2203 | + "filter": "WaitingDevicesFilter", | ||
2204 | + "keyFilters": [ | ||
2205 | + { | ||
2206 | + "key": { | ||
2207 | + "type": "TIME_SERIES", | ||
2208 | + "key": "sw_state" | ||
2209 | + }, | ||
2210 | + "valueType": "STRING", | ||
2211 | + "predicates": [ | ||
2212 | + { | ||
2213 | + "keyFilterPredicate": { | ||
2214 | + "operation": "EQUAL", | ||
2215 | + "value": { | ||
2216 | + "defaultValue": "QUEUED", | ||
2217 | + "dynamicValue": null | ||
2218 | + }, | ||
2219 | + "ignoreCase": false, | ||
2220 | + "type": "STRING" | ||
2221 | + }, | ||
2222 | + "userInfo": { | ||
2223 | + "editable": true, | ||
2224 | + "label": "", | ||
2225 | + "autogeneratedLabel": true, | ||
2226 | + "order": 0 | ||
2227 | + } | ||
2228 | + } | ||
2229 | + ] | ||
2230 | + } | ||
2231 | + ], | ||
2232 | + "editable": false | ||
2233 | + }, | ||
2234 | + "579f0468-9ce9-7e3e-b34c-88dd3de59897": { | ||
2235 | + "id": "579f0468-9ce9-7e3e-b34c-88dd3de59897", | ||
2236 | + "filter": "UpdatingDevicesFilter", | ||
2237 | + "keyFilters": [ | ||
2238 | + { | ||
2239 | + "key": { | ||
2240 | + "type": "TIME_SERIES", | ||
2241 | + "key": "sw_state" | ||
2242 | + }, | ||
2243 | + "valueType": "STRING", | ||
2244 | + "predicates": [ | ||
2245 | + { | ||
2246 | + "keyFilterPredicate": { | ||
2247 | + "operation": "OR", | ||
2248 | + "predicates": [ | ||
2249 | + { | ||
2250 | + "keyFilterPredicate": { | ||
2251 | + "operation": "EQUAL", | ||
2252 | + "value": { | ||
2253 | + "defaultValue": "INITIATED", | ||
2254 | + "dynamicValue": null | ||
2255 | + }, | ||
2256 | + "ignoreCase": false, | ||
2257 | + "type": "STRING" | ||
2258 | + }, | ||
2259 | + "userInfo": { | ||
2260 | + "editable": false, | ||
2261 | + "label": "sw_state equel", | ||
2262 | + "autogeneratedLabel": true, | ||
2263 | + "order": 0 | ||
2264 | + } | ||
2265 | + }, | ||
2266 | + { | ||
2267 | + "keyFilterPredicate": { | ||
2268 | + "operation": "EQUAL", | ||
2269 | + "value": { | ||
2270 | + "defaultValue": "DOWNLOADING", | ||
2271 | + "dynamicValue": null | ||
2272 | + }, | ||
2273 | + "ignoreCase": false, | ||
2274 | + "type": "STRING" | ||
2275 | + }, | ||
2276 | + "userInfo": { | ||
2277 | + "editable": false, | ||
2278 | + "label": "sw_state equal", | ||
2279 | + "autogeneratedLabel": true, | ||
2280 | + "order": 0 | ||
2281 | + } | ||
2282 | + }, | ||
2283 | + { | ||
2284 | + "keyFilterPredicate": { | ||
2285 | + "operation": "EQUAL", | ||
2286 | + "value": { | ||
2287 | + "defaultValue": "DOWNLOADED", | ||
2288 | + "dynamicValue": null | ||
2289 | + }, | ||
2290 | + "ignoreCase": false, | ||
2291 | + "type": "STRING" | ||
2292 | + }, | ||
2293 | + "userInfo": { | ||
2294 | + "editable": false, | ||
2295 | + "label": "sw_state equal", | ||
2296 | + "autogeneratedLabel": true, | ||
2297 | + "order": 0 | ||
2298 | + } | ||
2299 | + }, | ||
2300 | + { | ||
2301 | + "keyFilterPredicate": { | ||
2302 | + "operation": "EQUAL", | ||
2303 | + "value": { | ||
2304 | + "defaultValue": "VERIFIED", | ||
2305 | + "dynamicValue": null | ||
2306 | + }, | ||
2307 | + "ignoreCase": false, | ||
2308 | + "type": "STRING" | ||
2309 | + }, | ||
2310 | + "userInfo": { | ||
2311 | + "editable": false, | ||
2312 | + "label": "sw_state equal", | ||
2313 | + "autogeneratedLabel": true, | ||
2314 | + "order": 0 | ||
2315 | + } | ||
2316 | + }, | ||
2317 | + { | ||
2318 | + "keyFilterPredicate": { | ||
2319 | + "operation": "EQUAL", | ||
2320 | + "value": { | ||
2321 | + "defaultValue": "UPDATING", | ||
2322 | + "dynamicValue": null | ||
2323 | + }, | ||
2324 | + "ignoreCase": false, | ||
2325 | + "type": "STRING" | ||
2326 | + }, | ||
2327 | + "userInfo": { | ||
2328 | + "editable": false, | ||
2329 | + "label": "sw_state equal", | ||
2330 | + "autogeneratedLabel": true, | ||
2331 | + "order": 0 | ||
2332 | + } | ||
2333 | + } | ||
2334 | + ], | ||
2335 | + "type": "COMPLEX" | ||
2336 | + }, | ||
2337 | + "userInfo": { | ||
2338 | + "editable": true, | ||
2339 | + "label": "", | ||
2340 | + "autogeneratedLabel": true, | ||
2341 | + "order": 0 | ||
2342 | + } | ||
2343 | + } | ||
2344 | + ] | ||
2345 | + } | ||
2346 | + ], | ||
2347 | + "editable": false | ||
2348 | + }, | ||
2349 | + "6044e198-df64-cd76-f339-696f220c4943": { | ||
2350 | + "id": "6044e198-df64-cd76-f339-696f220c4943", | ||
2351 | + "filter": "UpdetedDevicesFilter", | ||
2352 | + "keyFilters": [ | ||
2353 | + { | ||
2354 | + "key": { | ||
2355 | + "type": "TIME_SERIES", | ||
2356 | + "key": "sw_state" | ||
2357 | + }, | ||
2358 | + "valueType": "STRING", | ||
2359 | + "predicates": [ | ||
2360 | + { | ||
2361 | + "keyFilterPredicate": { | ||
2362 | + "operation": "EQUAL", | ||
2363 | + "value": { | ||
2364 | + "defaultValue": "UPDATED", | ||
2365 | + "dynamicValue": null | ||
2366 | + }, | ||
2367 | + "ignoreCase": false, | ||
2368 | + "type": "STRING" | ||
2369 | + }, | ||
2370 | + "userInfo": { | ||
2371 | + "editable": true, | ||
2372 | + "label": "", | ||
2373 | + "autogeneratedLabel": true, | ||
2374 | + "order": 0 | ||
2375 | + } | ||
2376 | + } | ||
2377 | + ] | ||
2378 | + } | ||
2379 | + ], | ||
2380 | + "editable": false | ||
2381 | + }, | ||
2382 | + "bdbc6ea1-95a7-3912-341a-58dc7704a00f": { | ||
2383 | + "id": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", | ||
2384 | + "filter": "FailedDevicesFilter", | ||
2385 | + "keyFilters": [ | ||
2386 | + { | ||
2387 | + "key": { | ||
2388 | + "type": "TIME_SERIES", | ||
2389 | + "key": "sw_state" | ||
2390 | + }, | ||
2391 | + "valueType": "STRING", | ||
2392 | + "predicates": [ | ||
2393 | + { | ||
2394 | + "keyFilterPredicate": { | ||
2395 | + "operation": "EQUAL", | ||
2396 | + "value": { | ||
2397 | + "defaultValue": "FAILED", | ||
2398 | + "dynamicValue": null | ||
2399 | + }, | ||
2400 | + "ignoreCase": false, | ||
2401 | + "type": "STRING" | ||
2402 | + }, | ||
2403 | + "userInfo": { | ||
2404 | + "editable": true, | ||
2405 | + "label": "", | ||
2406 | + "autogeneratedLabel": true, | ||
2407 | + "order": 0 | ||
2408 | + } | ||
2409 | + } | ||
2410 | + ] | ||
2411 | + } | ||
2412 | + ], | ||
2413 | + "editable": false | ||
2414 | + }, | ||
2415 | + "8fdb88d0-50ac-2232-fdb7-69c30c16544e": { | ||
2416 | + "id": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", | ||
2417 | + "filter": "DeviceSearch", | ||
2418 | + "keyFilters": [ | ||
2419 | + { | ||
2420 | + "key": { | ||
2421 | + "type": "ENTITY_FIELD", | ||
2422 | + "key": "name" | ||
2423 | + }, | ||
2424 | + "valueType": "STRING", | ||
2425 | + "predicates": [ | ||
2426 | + { | ||
2427 | + "keyFilterPredicate": { | ||
2428 | + "operation": "CONTAINS", | ||
2429 | + "value": { | ||
2430 | + "defaultValue": "" | ||
2431 | + }, | ||
2432 | + "ignoreCase": true, | ||
2433 | + "type": "STRING" | ||
2434 | + }, | ||
2435 | + "userInfo": { | ||
2436 | + "editable": true, | ||
2437 | + "label": "Device name", | ||
2438 | + "autogeneratedLabel": false, | ||
2439 | + "order": 0 | ||
2440 | + } | ||
2441 | + } | ||
2442 | + ] | ||
2443 | + } | ||
2444 | + ], | ||
2445 | + "editable": true | ||
2446 | + } | ||
2447 | + }, | ||
2448 | + "timewindow": { | ||
2449 | + "displayValue": "", | ||
2450 | + "hideInterval": false, | ||
2451 | + "hideAggregation": false, | ||
2452 | + "hideAggInterval": false, | ||
2453 | + "hideTimezone": false, | ||
2454 | + "selectedTab": 0, | ||
2455 | + "realtime": { | ||
2456 | + "realtimeType": 0, | ||
2457 | + "interval": 1000, | ||
2458 | + "timewindowMs": 60000, | ||
2459 | + "quickInterval": "CURRENT_DAY" | ||
2460 | + }, | ||
2461 | + "history": { | ||
2462 | + "historyType": 0, | ||
2463 | + "interval": 1000, | ||
2464 | + "timewindowMs": 60000, | ||
2465 | + "fixedTimewindow": { | ||
2466 | + "startTimeMs": 1618998609030, | ||
2467 | + "endTimeMs": 1619085009030 | ||
2468 | + }, | ||
2469 | + "quickInterval": "CURRENT_DAY" | ||
2470 | + }, | ||
2471 | + "aggregation": { | ||
2472 | + "type": "AVG", | ||
2473 | + "limit": 25000 | ||
2474 | + } | ||
2475 | + }, | ||
2476 | + "settings": { | ||
2477 | + "stateControllerId": "entity", | ||
2478 | + "showTitle": false, | ||
2479 | + "showDashboardsSelect": false, | ||
2480 | + "showEntitiesSelect": false, | ||
2481 | + "showDashboardTimewindow": true, | ||
2482 | + "showDashboardExport": false, | ||
2483 | + "toolbarAlwaysOpen": true, | ||
2484 | + "titleColor": "rgba(0,0,0,0.870588)", | ||
2485 | + "showFilters": true, | ||
2486 | + "showDashboardLogo": false, | ||
2487 | + "dashboardLogoUrl": null, | ||
2488 | + "showUpdateDashboardImage": false | ||
2489 | + } | ||
2490 | + }, | ||
2491 | + "name": "Software" | ||
2492 | +} |
1 | +{ | ||
2 | + "providerId": "Apple", | ||
3 | + "additionalInfo": null, | ||
4 | + "accessTokenUri": "https://appleid.apple.com/auth/token", | ||
5 | + "authorizationUri": "https://appleid.apple.com/auth/authorize?response_mode=form_post", | ||
6 | + "scope": ["email","openid","name"], | ||
7 | + "jwkSetUri": "https://appleid.apple.com/auth/keys", | ||
8 | + "userInfoUri": null, | ||
9 | + "clientAuthenticationMethod": "POST", | ||
10 | + "userNameAttributeName": "email", | ||
11 | + "mapperConfig": { | ||
12 | + "type": "APPLE", | ||
13 | + "basic": { | ||
14 | + "emailAttributeKey": "email", | ||
15 | + "firstNameAttributeKey": "firstName", | ||
16 | + "lastNameAttributeKey": "lastName", | ||
17 | + "tenantNameStrategy": "DOMAIN" | ||
18 | + } | ||
19 | + }, | ||
20 | + "comment": null, | ||
21 | + "loginButtonIcon": "apple-logo", | ||
22 | + "loginButtonLabel": "Apple", | ||
23 | + "helpLink": "https://developer.apple.com/sign-in-with-apple/get-started/" | ||
24 | +} |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | "alias": "html_card", | 28 | "alias": "html_card", |
29 | "name": "HTML Card", | 29 | "name": "HTML Card", |
30 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=", | 30 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=", |
31 | - "description": "Useful to inject custom HTML code. Designed to displays static information only.", | 31 | + "description": "Useful to inject custom HTML code. Designed to display static information only.", |
32 | "descriptor": { | 32 | "descriptor": { |
33 | "type": "static", | 33 | "type": "static", |
34 | "sizeX": 7.5, | 34 | "sizeX": 7.5, |
@@ -10,16 +10,16 @@ | @@ -10,16 +10,16 @@ | ||
10 | "alias": "rpc_debug_terminal", | 10 | "alias": "rpc_debug_terminal", |
11 | "name": "RPC debug terminal", | 11 | "name": "RPC debug terminal", |
12 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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", | 12 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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", |
13 | - "description": "Allows to send any RPC command using it's name and parameters to device. Useful for debug.", | 13 | + "description": "Allows to send any RPC command using its name and parameters to device. Useful for debug.", |
14 | "descriptor": { | 14 | "descriptor": { |
15 | "type": "rpc", | 15 | "type": "rpc", |
16 | "sizeX": 9.5, | 16 | "sizeX": 9.5, |
17 | "sizeY": 5.5, | 17 | "sizeY": 5.5, |
18 | "resources": [], | 18 | "resources": [], |
19 | "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", | 19 | "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", |
20 | - "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", | ||
21 | - "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var utils = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('utils'));\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = utils.guid();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n \nself.onDestroy = function() {\n}", | ||
22 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\"\n ]\n}", | 20 | + "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", |
21 | + "controllerScript": "var requestTimeout = 500;\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 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 spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\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 (!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:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\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\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 },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", | ||
23 | "dataKeySettingsSchema": "{}\n", | 23 | "dataKeySettingsSchema": "{}\n", |
24 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | 24 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
25 | } | 25 | } |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | "alias": "rpc_remote_shell", | 28 | "alias": "rpc_remote_shell", |
29 | "name": "RPC remote shell", | 29 | "name": "RPC remote shell", |
30 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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", | 30 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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", |
31 | - "description": "Allows to send emulate remote shell. Requires custom implementation on the target device to work properly.", | 31 | + "description": "Allows to emulate remote shell. Requires custom implementation on the target device to work properly.", |
32 | "descriptor": { | 32 | "descriptor": { |
33 | "type": "rpc", | 33 | "type": "rpc", |
34 | "sizeX": 9.5, | 34 | "sizeX": 9.5, |
@@ -151,4 +151,4 @@ | @@ -151,4 +151,4 @@ | ||
151 | } | 151 | } |
152 | } | 152 | } |
153 | ] | 153 | ] |
154 | -} | ||
154 | +} |
@@ -100,7 +100,7 @@ | @@ -100,7 +100,7 @@ | ||
100 | "alias": "lcd_bar_gauge", | 100 | "alias": "lcd_bar_gauge", |
101 | "name": "LCD bar gauge", | 101 | "name": "LCD bar gauge", |
102 | "image": "data:image/png;base64,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", | 102 | "image": "data:image/png;base64,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", |
103 | - "description": "Preconfigured gauge to display any value reading as an bar. Allows to configure value range, gradient colors and other settings.", | 103 | + "description": "Preconfigured gauge to display any value reading as a bar. Allows to configure value range, gradient colors and other settings.", |
104 | "descriptor": { | 104 | "descriptor": { |
105 | "type": "latest", | 105 | "type": "latest", |
106 | "sizeX": 2, | 106 | "sizeX": 2, |
@@ -10,7 +10,7 @@ | @@ -10,7 +10,7 @@ | ||
10 | "alias": "device_admin_table", | 10 | "alias": "device_admin_table", |
11 | "name": "Device admin table", | 11 | "name": "Device admin table", |
12 | "image": "data:image/png;base64,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", | 12 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAmQSURBVHja7d39V1NHGsBx/7Lgafd47FH2BhAEDIFoyIL1pb5gC4upcqwW0aJUDNpSGmFXAxVQV+silUUsqQVsFvAFRF6EKEFISSAvJDf3uz8ELOtaCZCzKp35AThzZ+7cz5lnbubkCbmrCI49GXzHy5OxAKuCwy6Zd7zIruHgqjEXK6C4xlY9kVcCRH6yapAVUQYFREAEREAEREDeGsiMbWpR7bsd4d9BmyvKkOajR4+e7VBe0bC8NoKzjUrdL9WUXXlde03d7E5JalvENdvtC0Mqky9aipIPzfxvw5obS4Kcb3ppzgxty4aYTBFAMgBHWnl4I/aK9sG5Tdr8i3tRNwcJACH5Fd3wSy3z+mjqwkdmIXKUIdQm+cC2Q9JVK+60OxDKrqPgDHhLEuNyHoFSm6beYQv3CFVqpM03gP6d0haL1M2Vj6rTpT3204lx+U7Ir8CfemW3pGkMz2uqlLQXrFuljaUB0Hz5cXzKxVnIqDEu9YQ/qpBe6QH98RbHrQ2tFByDh+pn5JbAoexux8k0F9+l3nF8lRJenxc0tsk6dR/BzE96H+VL3dRK5SO29KSSYVt6Kewpwydl3Bn5coMLYGpIuvKcXnXdZGeKBTSpt0fOS7/gktrw6j+339OXRxUyIVkpygNOGmlOnqFiH+SWMCLdheC29mBqHYTSw8v43kNQkq9wW+2AEamb2lQFvt4ow5nds5BGcEhdv4XW01ag6FPQXADyDuOS2ria4ocbScrr17nJZDLt328ymUz2CCDD0l0MO0pLS/ca8CdbybwGuSX8KIVvrk+kQ6Wlpelfhbt0nS4wJtRi0c0u9to04LweOJc9C7n923IOrxFH5WGjLm92sX+7HZfUxgltaWnpIWkimhCr9IwtH5vNZrMFTh5/HO+C3BJapXAE90snzGaz+UcAvt948a4tpZaqrMghT1JK7tgOzEEqs3BJbRzLNJvNZrMriqGlHNgN+4/OVv07peIQkFvCgPQYaBzxq+fdU3eYgbRaGjd4IoZUbgdO5oGmFjhsxCW1cX5LKLq3X1/P0cReuKFuJXS+HkKbE1rCkNCHB2doVj/hcKadqc97AdjzV3+wRqpmOvW0HDyzICQUXw+WFDu21H2gyXZgi2vEJbUxGP9tCOsXSpQgkiSpDwwAijkuXWN4DJhTZsIQhrITdUmN4MpX6xMLvOFdhmZD4sHMUmhJTNz4xYIQiuI+YmqXOjnzYAZoDqdskg6Hwkdvpabokm9FaUamRkcdgdm/PQ8ehQD8EwATLiDU2+0B4Hnn6FwX770RxekEXJ1jyugM0w5gagyYeg4TLkKjfsI/AEIPBiDU1xvwjCo4fFOdA8wdDfTd80driyJ2vwIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiICsOoti94LMv8VwTdrvdbve9DRBZtQ9aVUv8lOP+dWti1q2zvh0Q1S1aVTKBniGYdD7vkX33fwXsDwKRnO37D2DCiWL3jXke2QFv1/M3BNmZ4GlVyfbkTPVJTiVo123Xpq59SllsmnY6Qsi3qTyIGdUlad+rZkC9+YPrbwbSrD/RqpLv/YObf1JOZSntql4l4dJgzJCSWRMhxB4zUL4V3RkurQ19XMgP8W8GcrvnvXKVPJmfnqqST+XQq5okw/JDTHLymuIIIWSd21KLzsKAanxDbHKCyvtmIJx6XyWXfRjqmAe5u/qZ2+mLFPJd8monOgttMTMGk9vtVN4QxJuokqvXfZ2hmnwBkQ3Z5oy2SCHO1btBt6Ei7VMa1pwp3PsmQitUNQL3qhT5ytnOqomOJpxVfq4+wFd7OpJ0K/3VANqroDN9Y/FD51ffOd/VV3Znxdop0Fne+S3K45w24HSr2GsJiIAIiIAIiIAIiIAIiIAIyIKQoRVRRGgJiIAIiIAIiIAIiID8YSG2c6/4r6uh4uLi4qF3CmLLMhS9JOkxvCg9C0OUQJSuZHnfS/R8PM9gOPdS3cUX5flrIZdSwKLN3OcH2N+mrAd7Rr81Qa/LebrAuO5Yvebg+Pya5uPLcXRkN4/nZdleqrUWzxbra0PLkRHL8KYgB64C5FiVGJwaKy25UJO7wMCu9XBNJ4N3fiJXdgJ4/It3ZBn+0jz+soPLc5F1+bWQT6yxTPZB4fU5iDfzKrTkQm96BBB2dFChzzzri/dz1tJQSEdajn6MM4Yt5sU7DIat4ywNcuOYJxbgsd43B9mjV6Alu71l298jgRy/1JMphzYPH7mFdryhUEke5NrZzq2KnOZYZFwZDIasn1ka5FfdtCcWmNA9ZhaiMu2wQEtK2d4CIoF8du1iwq5d6tsdBQO7aSh0qQH+lrhr15/bo+KIDFKXpt+8OgtPVjt0/wI7O5QYJhK6aMnFFT8WASSQNHj9iNvtDoa0Z6/TUCivD+F31h93u91yVByRrhE8sQR3lvX0DLSnP/4pzqXEwN2kyZZcqDqyEGRtj3XvSZyJbcPFbsrWe2goxFg1dqR+LLFj6LgnKo6IITOncBYVFRVV0Gg80otyDLjW+KgO/CcW+FSJt6iopBUYOJrfBMM1cP863nLjZegrzL+9yHWe3cGyIG9DOfb78/HOQX5vPqBxDtL49kNqiot/14HvXPiFvdIntvECIiACIiACIiACIiACsmiIyLOL0BIQAREQAREQAREQAXmnISLPfmcahupvht8fnrx63QNg/yWyYYNNTa3//QXKo13LcowvPc+u7ee6tvqMdgpwbqquzAiCvHlnZOO61lSdSrk5v+bhteU4OrYuPc+u7Q/EPoPj1UBXPWT1QVV+pJD1MB43iedSvdt7Awa6Rmwot853w1T9Jc+iHdnLyLNr+3sygEA4tkL3N3kY2tq5CAif/hDQX6jRyRoHBS0NhZiMNzNbfbqaC4bQoh3LyLNr++9uD0/g5SbIi/+G0M7ersVATtT9K9/t3ttlrg5sDDYUyut9DDT/s8Dt3ta7eMeS8+xo+4e0gNNhvdwEyPr7dduazmnbI4fkNddojEZj99Ot1uM0FLoSAKq0RqOxNyqOiCFy4iB8Vg9YayDv5+aqqi+Sv48YMqCetuaAB3bs7aShkDgXfT825YMnOo6IIfykKTu0fQZwpplOZAeAiEPr/aL9qR2Ecg6WbXZzeaNCQyFXsiv0D+Wdn5Xp/6959i4v/GrtDq9L388dMsB0T2TDy+3tfTIQ6mr1gLcfJgZhxDoJcmerd5EOkWd/uyAizy628QIiIAIiIALyB4esmAcEr4xHNk+OrQqsjIdoy6tWxmPNZf4DJqTD+Gup8cgAAAAASUVORK5CYII=", |
13 | - "description": "Customized entity table widget with preconfigured actions to create update and delete devices.", | 13 | + "description": "Customized entity table widget with preconfigured actions to create, update and delete devices.", |
14 | "descriptor": { | 14 | "descriptor": { |
15 | "type": "latest", | 15 | "type": "latest", |
16 | "sizeX": 7.5, | 16 | "sizeX": 7.5, |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | "alias": "asset_admin_table", | 28 | "alias": "asset_admin_table", |
29 | "name": "Asset admin table", | 29 | "name": "Asset admin table", |
30 | "image": "data:image/png;base64,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", | 30 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAm6SURBVHja7d3pV1PXGsBh/7LobW+txes9YYhiSsAoRKKtA63YWsHSWhwQcQATB4oo9QKptnWgqFhFMbEFLBVEoggyRMIUEwImJif53Q8BynVd5YDpUuneH5KwszmbZ+Xd+7xrveSceQQHuh+95a17IMC8YI9H5i1vsqcnOG/AwxxonoF53fJcgMjd8x4xJ9ojAREQAREQARGQNwDS2Tmj4f13J+Zv/8sgNftCszjON7nPdbRk+18yvHzV+Iv87JnP5XAogYRSpdsvPUz2aUWQ9oLA/3ac3BktiMmkBNKUkJM3/jL43HMku1x7fHJsMDyOD01AApMP4y08mZIWZUY6ghOQ4BSIHH1I3s6GBC8QKl6uXlMPoZLlaqMNaFonrbTQoFVrtJGgaf1E0uwcgdBhjSZvSy4B7amM2OXVDQYp5RrUaYMc2XFME/fVGIBXmxCrbeLJ7gT1xjYo15fo1FuHI5BwZVLshuYoQ7yaW6GUc8BpXcvg0YR+zmrvDJXE9dIeV9lfG3/L71xd5AwDDC89Otxu2AdnNDW959S5BCTj3Z6C2PSmnn0aL9ekIAfVR3vrNRUAIWfeRqefzzO7BnelhCiXCrub0r6KQP6jq+8/pB2NLuRCUpDijUD+p2Gene7lwIYw8ukedm8FCnL+DC1XnR/KDKAvBnJyCUg10C9Vg0O6G4Gsm7J6ijKBm33QJnVTrguBTd1PfjbPEs+DrL2oaJ2bTCZTVpbJZDI5Xg7JMMND6SG0JRnLOoEHuvSTD4G09UVFRRnGKWvEU7E7e7WeMelm5M8NSNfAK9nAJTVGIJunLOeiTCB4Zd+2TKk9stiHpSbys3ko5RYVFemORxXSKekMBoP6COC5lK3O8oO3Jlez5Sn6z0pLS0sr/oS4V223Nh3Q45HqlUN2Gi81VU9Ankj15Gdjlw6WlpaW2qIaWkdW19XV1RV9+AxbL/Sqr/BrDzjjqvliYi+bgFzWhKBCD8vPANsVQZ5ITdAhtVOuB+5I3eRn45Xqor79BnTlAMPqG2ze4qNTfYutm5/SE1tHtdpG6NQ5+CQ/MrZW3UpHmg6O6HtpS5wWUpwawqsuC3tyJTvlUnV4bMuGMPnZkGPsx5P7MJqQOrUzctLbRq9xWVrs/hCOj5amxe4NES6JTUkydMIZSecBkHOk5Um71WN4MyTd6sxpIS1x2t/4IXZpfJF0g3JD5oexOnvk3Sefq1M1uf5oQkb6xzfhfgg9bB4ECHfeifSOtraHADraxnOYrja/3xmE0P0HAc8Twk4fhJz+yIPPGcYzDLhdE/lVyxgMt7hwjuIdDrU3+ybfHRifIpopikjjBURABERABERABERABERABERABERABERABERABERA5hgk7HgKPscsjzXscDgcDt+bAJFVmWBVzfK/HLMWL5y/eLHtzYCormNVyQTsXeB2DdplX6sbcNwLKDnazx/AsIuwwzcw9sABPI2UJl4DZH38mFUlOxLT1PspjE9e/HGydtFjzEt0yaMKIce13Jvv1C9NfqeSTvXKD6pfD6Q2dZ9VJd89zy//DBemhxtU98PxPz2a3xVOsyiEOOZ3Fq9Bf5ifFoU27+JK3OuB1NnfKVbJ7q0rtCq5cBP3VW5WVFyZn5i4sEAhhPQTq86gr6BTNZSwJDFe9fT1QCh8VyWb14Yap0BuL+gbcfmUQr5PXOBCX0H9/GcG08iIK/yaIE81Krly8bEVKvckRDYYS1fUK4W4FmSAPqFEt41LCw/v+vR1hFaorBfuloXlc0fulA03XsVV5ufCPXxnDl1XcrSOSoDkC6A3fVvhhztHv3e9rWd2V8kiL+gr3voU5eGmeuCQVeRaAiIgAiIgAiIgAiIgAiIgAjItpGtONBFaAiIgAiIgAiIgAiIgf1tI04n/8x2yroKCgoKutwrSlG7Ie05iN0w2+0wgoQCAPPPKuwwXALgYZLZXJxoc2mIwnHiu7/RkG3wx5McckNP+OLUsNWX7GMCJlNStQU6sSN2h4LIDI0tSV6ZPXAPh0EXeA0A9Wps/O0ejsXZoS3rTc722gvFme0loBZbZqf6UkmOEdh8FHuhlMn/pSpbZ0Dj9xJ5/gTX9z5/fAzyoIwX60SAQmEmFtzHdsLp26HkHZyci6+zL1sjlTfLydkqOwc9fAa4OyKkFyFAIaTSwt4qB5ey9wHuMGtdsWDh6aRe6L9fF27mc8NH6PTNwGAxrhpgdJJz2zQ4o+bqh5sPxT+5eehDMxh0Kisyef2xct6RtKuTkATzvj17ahe5XKg/JUh+lexTHlcFgSP+NWUJofHcASozm1O8oS10LzpQeYKAx5YECyOIR9xV9eApkmxXUo5d2oeujOu+xDn7e84oOpRCXBJQcw64NArhT70BfA5grlYUW74/uOz8J2VkzFeKOVwx5sWOmEHIsgN943G7v6ltm717VqgCyyN5avBrLth7zOMRq6KxaMAlhraVj455XdCiFjB0CrDfAcRhw5uXl5ZXRvP1LJdXmp3l5e06N4DNlXTjG5WYKoCrLctTfWs23blouMrxn+4F9Ste58QXbi0LIX9rO9PkyLykYt+fFn8ebAfn9izVlYWUQ4wu3+5oJSM2bn2tZCgpefNrynYic2E/6RBovIAIiIAIiIAIiIAIiIDOGiDq7CC0BERABERABERABEZC3GiLq7DfdIF9n6OrVOieEb/1oB5CvKazGtp+96o+iY2jWdXZic8Abg01bVqytZtfX55OvAeWqx4omtqRaDiV7o+ZoXDPrOjuxmht4Y7BtgpaVwVSZmm/AYUx8zLOqyq5pJh6RPGCqocp+EWdllQ/H71Dj62ir/sE9G4fxFerssdYE7zjEZgQoshDOuJf0mOyD1ZppLgbR/HHkOSazujfxXGmaXLMTEocs8T+a9PJsHK9QZ48dPLrTG4NNnf2ZtgW4vUbm3EGSHmOsCnRM8/3h65/jNJttxIxReAqyro9DTGBsnpVj9nX22MFA8tUYbEb7D+uBjhQnQ1LV1fgzo/37k/c+e/ncf6Qz2rDbTEyAnFowVY5DzJBzPVoOxRBa4mKwbSKcbqVf3wm9ZWVlS0zDFuQtV18++bN/d8J3ZmICHP8WMqzXc5DjhiwHIeV+tBzKIRRG1sjvybI+w2w2y0DSY3JzDmt7p5neurRwv+Y3YgJ49IVfb5JdcabNC4cs8aatmVFzKIT8EQD/bdz3gaaxxoaGhoYQ0OwnbL85/dbjsd4agdsh8Dc0hWDI2tcSsJhbG+WZO968OrvFPNPfeEPr7B13ZwERdXaRxguIgAiIgAjI3xwyZ24QPDdu2ewemBeYGzfRlufNjduay/wXtgu0d9kLWo8AAAAASUVORK5CYII=", |
31 | - "description": "Customized entity table widget with preconfigured actions to create update and delete assets.", | 31 | + "description": "Customized entity table widget with preconfigured actions to create, update and delete assets.", |
32 | "descriptor": { | 32 | "descriptor": { |
33 | "type": "latest", | 33 | "type": "latest", |
34 | "sizeX": 7.5, | 34 | "sizeX": 7.5, |
@@ -10,7 +10,7 @@ | @@ -10,7 +10,7 @@ | ||
10 | "alias": "gateway_configuration", | 10 | "alias": "gateway_configuration", |
11 | "name": "Gateway Configuration", | 11 | "name": "Gateway Configuration", |
12 | "image": "data:image/png;base64,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", | 12 | "image": "data:image/png;base64,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", |
13 | - "description": "Allows to define widget gateway configuration for a single selected device.", | 13 | + "description": "Allows to define gateway configuration for a single selected device.", |
14 | "descriptor": { | 14 | "descriptor": { |
15 | "type": "static", | 15 | "type": "static", |
16 | "sizeX": 8, | 16 | "sizeX": 8, |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | "alias": "update_multiple_attributes", | 28 | "alias": "update_multiple_attributes", |
29 | "name": "Update Multiple Attributes", | 29 | "name": "Update Multiple Attributes", |
30 | "image": "data:image/png;base64,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", | 30 | "image": "data:image/png;base64,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", |
31 | - "description": "Allows to create an input form and set values for multiple attributes simultaniously.", | 31 | + "description": "Allows to create an input form and set values for multiple attributes simultaneously.", |
32 | "descriptor": { | 32 | "descriptor": { |
33 | "type": "latest", | 33 | "type": "latest", |
34 | "sizeX": 7.5, | 34 | "sizeX": 7.5, |
@@ -46,7 +46,7 @@ | @@ -46,7 +46,7 @@ | ||
46 | "alias": "route_map", | 46 | "alias": "route_map", |
47 | "name": "Route Map", | 47 | "name": "Route Map", |
48 | "image": "data:image/png;base64,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", | 48 | "image": "data:image/png;base64,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", |
49 | - "description": "TTrip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", | 49 | + "description": "Trip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", |
50 | "descriptor": { | 50 | "descriptor": { |
51 | "type": "timeseries", | 51 | "type": "timeseries", |
52 | "sizeX": 8.5, | 52 | "sizeX": 8.5, |
@@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package ( | @@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package ( | ||
67 | type varchar(32) NOT NULL, | 67 | type varchar(32) NOT NULL, |
68 | title varchar(255) NOT NULL, | 68 | title varchar(255) NOT NULL, |
69 | version varchar(255) NOT NULL, | 69 | version varchar(255) NOT NULL, |
70 | + url varchar(255), | ||
70 | file_name varchar(255), | 71 | file_name varchar(255), |
71 | content_type varchar(255), | 72 | content_type varchar(255), |
72 | checksum_algorithm varchar(32), | 73 | checksum_algorithm varchar(32), |
@@ -78,6 +79,68 @@ CREATE TABLE IF NOT EXISTS ota_package ( | @@ -78,6 +79,68 @@ CREATE TABLE IF NOT EXISTS ota_package ( | ||
78 | CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) | 79 | CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) |
79 | ); | 80 | ); |
80 | 81 | ||
82 | +CREATE TABLE IF NOT EXISTS oauth2_params ( | ||
83 | + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY, | ||
84 | + enabled boolean, | ||
85 | + tenant_id uuid, | ||
86 | + created_time bigint NOT NULL | ||
87 | +); | ||
88 | + | ||
89 | +CREATE TABLE IF NOT EXISTS oauth2_registration ( | ||
90 | + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, | ||
91 | + oauth2_params_id uuid NOT NULL, | ||
92 | + created_time bigint NOT NULL, | ||
93 | + additional_info varchar, | ||
94 | + client_id varchar(255), | ||
95 | + client_secret varchar(2048), | ||
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 | + app_secret varchar(2048), | ||
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 | + | ||
81 | ALTER TABLE dashboard | 144 | ALTER TABLE dashboard |
82 | ADD COLUMN IF NOT EXISTS image varchar(1000000); | 145 | ADD COLUMN IF NOT EXISTS image varchar(1000000); |
83 | 146 |
@@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService; | @@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService; | ||
60 | import org.thingsboard.server.dao.entityview.EntityViewService; | 60 | import org.thingsboard.server.dao.entityview.EntityViewService; |
61 | import org.thingsboard.server.dao.event.EventService; | 61 | import org.thingsboard.server.dao.event.EventService; |
62 | import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | 62 | import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; |
63 | +import org.thingsboard.server.dao.ota.OtaPackageService; | ||
63 | import org.thingsboard.server.dao.relation.RelationService; | 64 | import org.thingsboard.server.dao.relation.RelationService; |
65 | +import org.thingsboard.server.dao.resource.ResourceService; | ||
64 | import org.thingsboard.server.dao.rule.RuleChainService; | 66 | import org.thingsboard.server.dao.rule.RuleChainService; |
65 | import org.thingsboard.server.dao.rule.RuleNodeStateService; | 67 | import org.thingsboard.server.dao.rule.RuleNodeStateService; |
66 | import org.thingsboard.server.dao.tenant.TenantProfileService; | 68 | import org.thingsboard.server.dao.tenant.TenantProfileService; |
@@ -311,6 +313,14 @@ public class ActorSystemContext { | @@ -311,6 +313,14 @@ public class ActorSystemContext { | ||
311 | @Autowired(required = false) | 313 | @Autowired(required = false) |
312 | @Getter private EdgeRpcService edgeRpcService; | 314 | @Getter private EdgeRpcService edgeRpcService; |
313 | 315 | ||
316 | + @Lazy | ||
317 | + @Autowired(required = false) | ||
318 | + @Getter private ResourceService resourceService; | ||
319 | + | ||
320 | + @Lazy | ||
321 | + @Autowired(required = false) | ||
322 | + @Getter private OtaPackageService otaPackageService; | ||
323 | + | ||
314 | @Value("${actors.session.max_concurrent_sessions_per_device:1}") | 324 | @Value("${actors.session.max_concurrent_sessions_per_device:1}") |
315 | @Getter | 325 | @Getter |
316 | private long maxConcurrentSessionsPerDevice; | 326 | private long maxConcurrentSessionsPerDevice; |
@@ -192,6 +192,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -192,6 +192,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
192 | syncSessionSet.add(key); | 192 | syncSessionSet.add(key); |
193 | } | 193 | } |
194 | }); | 194 | }); |
195 | + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions); | ||
195 | syncSessionSet.forEach(rpcSubscriptions::remove); | 196 | syncSessionSet.forEach(rpcSubscriptions::remove); |
196 | } | 197 | } |
197 | 198 | ||
@@ -454,7 +455,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -454,7 +455,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
454 | } else { | 455 | } else { |
455 | SessionInfoMetaData sessionMD = sessions.get(sessionId); | 456 | SessionInfoMetaData sessionMD = sessions.get(sessionId); |
456 | if (sessionMD == null) { | 457 | if (sessionMD == null) { |
457 | - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); | 458 | + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); |
458 | } | 459 | } |
459 | sessionMD.setSubscribedToAttributes(true); | 460 | sessionMD.setSubscribedToAttributes(true); |
460 | log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); | 461 | log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); |
@@ -475,7 +476,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -475,7 +476,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
475 | } else { | 476 | } else { |
476 | SessionInfoMetaData sessionMD = sessions.get(sessionId); | 477 | SessionInfoMetaData sessionMD = sessions.get(sessionId); |
477 | if (sessionMD == null) { | 478 | if (sessionMD == null) { |
478 | - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); | 479 | + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); |
479 | } | 480 | } |
480 | sessionMD.setSubscribedToRPC(true); | 481 | sessionMD.setSubscribedToRPC(true); |
481 | log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); | 482 | log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); |
@@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService; | @@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService; | ||
69 | import org.thingsboard.server.dao.entityview.EntityViewService; | 69 | import org.thingsboard.server.dao.entityview.EntityViewService; |
70 | import org.thingsboard.server.dao.nosql.CassandraStatementTask; | 70 | import org.thingsboard.server.dao.nosql.CassandraStatementTask; |
71 | import org.thingsboard.server.dao.nosql.TbResultSetFuture; | 71 | import org.thingsboard.server.dao.nosql.TbResultSetFuture; |
72 | +import org.thingsboard.server.dao.ota.OtaPackageService; | ||
72 | import org.thingsboard.server.dao.relation.RelationService; | 73 | import org.thingsboard.server.dao.relation.RelationService; |
74 | +import org.thingsboard.server.dao.resource.ResourceService; | ||
73 | import org.thingsboard.server.dao.rule.RuleChainService; | 75 | import org.thingsboard.server.dao.rule.RuleChainService; |
74 | import org.thingsboard.server.dao.tenant.TenantService; | 76 | import org.thingsboard.server.dao.tenant.TenantService; |
75 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 77 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
@@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext { | @@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext { | ||
487 | } | 489 | } |
488 | 490 | ||
489 | @Override | 491 | @Override |
492 | + public ResourceService getResourceService() { | ||
493 | + return mainCtx.getResourceService(); | ||
494 | + } | ||
495 | + | ||
496 | + @Override | ||
497 | + public OtaPackageService getOtaPackageService() { | ||
498 | + return mainCtx.getOtaPackageService(); | ||
499 | + } | ||
500 | + | ||
501 | + @Override | ||
490 | public RuleEngineDeviceProfileCache getDeviceProfileCache() { | 502 | public RuleEngineDeviceProfileCache getDeviceProfileCache() { |
491 | return mainCtx.getDeviceProfileCache(); | 503 | return mainCtx.getDeviceProfileCache(); |
492 | } | 504 | } |
@@ -37,6 +37,9 @@ import org.springframework.util.StringUtils; | @@ -37,6 +37,9 @@ import org.springframework.util.StringUtils; | ||
37 | import org.springframework.web.util.UriComponents; | 37 | import org.springframework.web.util.UriComponents; |
38 | import org.springframework.web.util.UriComponentsBuilder; | 38 | import org.springframework.web.util.UriComponentsBuilder; |
39 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | 39 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
40 | +import org.thingsboard.server.dao.oauth2.OAuth2Service; | ||
41 | +import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames; | ||
42 | +import org.thingsboard.server.service.security.model.token.OAuth2AppTokenFactory; | ||
40 | import org.thingsboard.server.utils.MiscUtils; | 43 | import org.thingsboard.server.utils.MiscUtils; |
41 | 44 | ||
42 | import javax.servlet.http.HttpServletRequest; | 45 | import javax.servlet.http.HttpServletRequest; |
@@ -46,12 +49,13 @@ import java.security.NoSuchAlgorithmException; | @@ -46,12 +49,13 @@ import java.security.NoSuchAlgorithmException; | ||
46 | import java.util.Base64; | 49 | import java.util.Base64; |
47 | import java.util.HashMap; | 50 | import java.util.HashMap; |
48 | import java.util.Map; | 51 | import java.util.Map; |
52 | +import java.util.UUID; | ||
49 | 53 | ||
50 | @Service | 54 | @Service |
51 | @Slf4j | 55 | @Slf4j |
52 | public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { | 56 | 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/"; | 57 | + private static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; |
58 | + private static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; | ||
55 | private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; | 59 | private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; |
56 | private static final char PATH_DELIMITER = '/'; | 60 | private static final char PATH_DELIMITER = '/'; |
57 | 61 | ||
@@ -63,6 +67,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | @@ -63,6 +67,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | ||
63 | @Autowired | 67 | @Autowired |
64 | private ClientRegistrationRepository clientRegistrationRepository; | 68 | private ClientRegistrationRepository clientRegistrationRepository; |
65 | 69 | ||
70 | + @Autowired | ||
71 | + private OAuth2Service oAuth2Service; | ||
72 | + | ||
73 | + @Autowired | ||
74 | + private OAuth2AppTokenFactory oAuth2AppTokenFactory; | ||
75 | + | ||
66 | @Autowired(required = false) | 76 | @Autowired(required = false) |
67 | private OAuth2Configuration oauth2Configuration; | 77 | private OAuth2Configuration oauth2Configuration; |
68 | 78 | ||
@@ -71,7 +81,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | @@ -71,7 +81,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | ||
71 | public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { | 81 | public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { |
72 | String registrationId = this.resolveRegistrationId(request); | 82 | String registrationId = this.resolveRegistrationId(request); |
73 | String redirectUriAction = getAction(request, "login"); | 83 | String redirectUriAction = getAction(request, "login"); |
74 | - return resolve(request, registrationId, redirectUriAction); | 84 | + String appPackage = getAppPackage(request); |
85 | + String appToken = getAppToken(request); | ||
86 | + return resolve(request, registrationId, redirectUriAction, appPackage, appToken); | ||
75 | } | 87 | } |
76 | 88 | ||
77 | @Override | 89 | @Override |
@@ -80,7 +92,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | @@ -80,7 +92,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | ||
80 | return null; | 92 | return null; |
81 | } | 93 | } |
82 | String redirectUriAction = getAction(request, "authorize"); | 94 | String redirectUriAction = getAction(request, "authorize"); |
83 | - return resolve(request, registrationId, redirectUriAction); | 95 | + String appPackage = getAppPackage(request); |
96 | + String appToken = getAppToken(request); | ||
97 | + return resolve(request, registrationId, redirectUriAction, appPackage, appToken); | ||
84 | } | 98 | } |
85 | 99 | ||
86 | private String getAction(HttpServletRequest request, String defaultAction) { | 100 | private String getAction(HttpServletRequest request, String defaultAction) { |
@@ -91,8 +105,16 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | @@ -91,8 +105,16 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | ||
91 | return action; | 105 | return action; |
92 | } | 106 | } |
93 | 107 | ||
108 | + private String getAppPackage(HttpServletRequest request) { | ||
109 | + return request.getParameter("pkg"); | ||
110 | + } | ||
111 | + | ||
112 | + private String getAppToken(HttpServletRequest request) { | ||
113 | + return request.getParameter("appToken"); | ||
114 | + } | ||
115 | + | ||
94 | @SuppressWarnings("deprecation") | 116 | @SuppressWarnings("deprecation") |
95 | - private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { | 117 | + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) { |
96 | if (registrationId == null) { | 118 | if (registrationId == null) { |
97 | return null; | 119 | return null; |
98 | } | 120 | } |
@@ -104,6 +126,18 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | @@ -104,6 +126,18 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza | ||
104 | 126 | ||
105 | Map<String, Object> attributes = new HashMap<>(); | 127 | Map<String, Object> attributes = new HashMap<>(); |
106 | attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); | 128 | attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); |
129 | + if (!StringUtils.isEmpty(appPackage)) { | ||
130 | + if (StringUtils.isEmpty(appToken)) { | ||
131 | + throw new IllegalArgumentException("Invalid application token."); | ||
132 | + } else { | ||
133 | + String appSecret = this.oAuth2Service.findAppSecret(UUID.fromString(registrationId), appPackage); | ||
134 | + if (StringUtils.isEmpty(appSecret)) { | ||
135 | + throw new IllegalArgumentException("Invalid package: " + appPackage + ". No application secret found for Client Registration with given application package."); | ||
136 | + } | ||
137 | + String callbackUrlScheme = this.oAuth2AppTokenFactory.validateTokenAndGetCallbackUrlScheme(appPackage, appToken, appSecret); | ||
138 | + attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme); | ||
139 | + } | ||
140 | + } | ||
107 | 141 | ||
108 | OAuth2AuthorizationRequest.Builder builder; | 142 | OAuth2AuthorizationRequest.Builder builder; |
109 | if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { | 143 | if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { |
@@ -110,8 +110,11 @@ public class AlarmController extends BaseController { | @@ -110,8 +110,11 @@ public class AlarmController extends BaseController { | ||
110 | checkParameter(ALARM_ID, strAlarmId); | 110 | checkParameter(ALARM_ID, strAlarmId); |
111 | try { | 111 | try { |
112 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); | 112 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
113 | - checkAlarmId(alarmId, Operation.WRITE); | 113 | + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); |
114 | 114 | ||
115 | + logEntityAction(alarm.getOriginator(), alarm, | ||
116 | + getCurrentUser().getCustomerId(), | ||
117 | + ActionType.ALARM_DELETE, null); | ||
115 | sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED); | 118 | sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED); |
116 | 119 | ||
117 | return alarmService.deleteAlarm(getTenantId(), alarmId); | 120 | return alarmService.deleteAlarm(getTenantId(), alarmId); |
@@ -17,7 +17,6 @@ package org.thingsboard.server.controller; | @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.core.JsonProcessingException; | 18 | import com.fasterxml.jackson.core.JsonProcessingException; |
19 | import com.fasterxml.jackson.databind.ObjectMapper; | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | -import com.fasterxml.jackson.databind.node.ArrayNode; | ||
21 | import com.fasterxml.jackson.databind.node.ObjectNode; | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
22 | import lombok.Getter; | 21 | import lombok.Getter; |
23 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
@@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; | @@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; | ||
31 | import org.thingsboard.server.common.data.Customer; | 30 | import org.thingsboard.server.common.data.Customer; |
32 | import org.thingsboard.server.common.data.Dashboard; | 31 | import org.thingsboard.server.common.data.Dashboard; |
33 | import org.thingsboard.server.common.data.DashboardInfo; | 32 | import org.thingsboard.server.common.data.DashboardInfo; |
34 | -import org.thingsboard.server.common.data.DataConstants; | ||
35 | import org.thingsboard.server.common.data.Device; | 33 | import org.thingsboard.server.common.data.Device; |
36 | import org.thingsboard.server.common.data.DeviceInfo; | 34 | import org.thingsboard.server.common.data.DeviceInfo; |
37 | import org.thingsboard.server.common.data.DeviceProfile; | 35 | import org.thingsboard.server.common.data.DeviceProfile; |
@@ -80,10 +78,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; | @@ -80,10 +78,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; | ||
80 | import org.thingsboard.server.common.data.id.UserId; | 78 | import org.thingsboard.server.common.data.id.UserId; |
81 | import org.thingsboard.server.common.data.id.WidgetTypeId; | 79 | import org.thingsboard.server.common.data.id.WidgetTypeId; |
82 | import org.thingsboard.server.common.data.id.WidgetsBundleId; | 80 | import org.thingsboard.server.common.data.id.WidgetsBundleId; |
83 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
84 | -import org.thingsboard.server.common.data.kv.DataType; | ||
85 | -import org.thingsboard.server.common.data.kv.KvEntry; | ||
86 | -import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
87 | import org.thingsboard.server.common.data.page.PageData; | 81 | import org.thingsboard.server.common.data.page.PageData; |
88 | import org.thingsboard.server.common.data.page.PageLink; | 82 | import org.thingsboard.server.common.data.page.PageLink; |
89 | import org.thingsboard.server.common.data.page.SortOrder; | 83 | import org.thingsboard.server.common.data.page.SortOrder; |
@@ -96,9 +90,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; | @@ -96,9 +90,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; | ||
96 | import org.thingsboard.server.common.data.rule.RuleNode; | 90 | import org.thingsboard.server.common.data.rule.RuleNode; |
97 | import org.thingsboard.server.common.data.widget.WidgetTypeDetails; | 91 | import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
98 | import org.thingsboard.server.common.data.widget.WidgetsBundle; | 92 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
99 | -import org.thingsboard.server.common.msg.TbMsg; | ||
100 | -import org.thingsboard.server.common.msg.TbMsgDataType; | ||
101 | -import org.thingsboard.server.common.msg.TbMsgMetaData; | ||
102 | import org.thingsboard.server.dao.asset.AssetService; | 93 | import org.thingsboard.server.dao.asset.AssetService; |
103 | import org.thingsboard.server.dao.attributes.AttributesService; | 94 | import org.thingsboard.server.dao.attributes.AttributesService; |
104 | import org.thingsboard.server.dao.audit.AuditLogService; | 95 | import org.thingsboard.server.dao.audit.AuditLogService; |
@@ -129,6 +120,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; | @@ -129,6 +120,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; | ||
129 | import org.thingsboard.server.queue.discovery.PartitionService; | 120 | import org.thingsboard.server.queue.discovery.PartitionService; |
130 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; | 121 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
131 | import org.thingsboard.server.queue.util.TbCoreComponent; | 122 | import org.thingsboard.server.queue.util.TbCoreComponent; |
123 | +import org.thingsboard.server.service.action.RuleEngineEntityActionService; | ||
132 | import org.thingsboard.server.service.component.ComponentDiscoveryService; | 124 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
133 | import org.thingsboard.server.service.edge.rpc.EdgeRpcService; | 125 | import org.thingsboard.server.service.edge.rpc.EdgeRpcService; |
134 | import org.thingsboard.server.service.ota.OtaPackageStateService; | 126 | import org.thingsboard.server.service.ota.OtaPackageStateService; |
@@ -151,11 +143,9 @@ import javax.servlet.http.HttpServletResponse; | @@ -151,11 +143,9 @@ import javax.servlet.http.HttpServletResponse; | ||
151 | import java.util.ArrayList; | 143 | import java.util.ArrayList; |
152 | import java.util.Collections; | 144 | import java.util.Collections; |
153 | import java.util.List; | 145 | import java.util.List; |
154 | -import java.util.Map; | ||
155 | import java.util.Optional; | 146 | import java.util.Optional; |
156 | import java.util.Set; | 147 | import java.util.Set; |
157 | import java.util.UUID; | 148 | import java.util.UUID; |
158 | -import java.util.stream.Collectors; | ||
159 | 149 | ||
160 | import static org.thingsboard.server.dao.service.Validator.validateId; | 150 | import static org.thingsboard.server.dao.service.Validator.validateId; |
161 | 151 | ||
@@ -282,6 +272,9 @@ public abstract class BaseController { | @@ -282,6 +272,9 @@ public abstract class BaseController { | ||
282 | @Autowired(required = false) | 272 | @Autowired(required = false) |
283 | protected EdgeRpcService edgeGrpcService; | 273 | protected EdgeRpcService edgeGrpcService; |
284 | 274 | ||
275 | + @Autowired | ||
276 | + protected RuleEngineEntityActionService ruleEngineEntityActionService; | ||
277 | + | ||
285 | @Value("${server.log_controller_error_stack_trace}") | 278 | @Value("${server.log_controller_error_stack_trace}") |
286 | @Getter | 279 | @Getter |
287 | private boolean logControllerErrorStackTrace; | 280 | private boolean logControllerErrorStackTrace; |
@@ -812,7 +805,7 @@ public abstract class BaseController { | @@ -812,7 +805,7 @@ public abstract class BaseController { | ||
812 | customerId = user.getCustomerId(); | 805 | customerId = user.getCustomerId(); |
813 | } | 806 | } |
814 | if (e == null) { | 807 | if (e == null) { |
815 | - pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo); | 808 | + ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo); |
816 | } | 809 | } |
817 | auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); | 810 | auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); |
818 | } | 811 | } |
@@ -822,184 +815,6 @@ public abstract class BaseController { | @@ -822,184 +815,6 @@ public abstract class BaseController { | ||
822 | return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; | 815 | return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; |
823 | } | 816 | } |
824 | 817 | ||
825 | - private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId, | ||
826 | - ActionType actionType, Object... additionalInfo) { | ||
827 | - String msgType = null; | ||
828 | - switch (actionType) { | ||
829 | - case ADDED: | ||
830 | - msgType = DataConstants.ENTITY_CREATED; | ||
831 | - break; | ||
832 | - case DELETED: | ||
833 | - msgType = DataConstants.ENTITY_DELETED; | ||
834 | - break; | ||
835 | - case UPDATED: | ||
836 | - msgType = DataConstants.ENTITY_UPDATED; | ||
837 | - break; | ||
838 | - case ASSIGNED_TO_CUSTOMER: | ||
839 | - msgType = DataConstants.ENTITY_ASSIGNED; | ||
840 | - break; | ||
841 | - case UNASSIGNED_FROM_CUSTOMER: | ||
842 | - msgType = DataConstants.ENTITY_UNASSIGNED; | ||
843 | - break; | ||
844 | - case ATTRIBUTES_UPDATED: | ||
845 | - msgType = DataConstants.ATTRIBUTES_UPDATED; | ||
846 | - break; | ||
847 | - case ATTRIBUTES_DELETED: | ||
848 | - msgType = DataConstants.ATTRIBUTES_DELETED; | ||
849 | - break; | ||
850 | - case ALARM_ACK: | ||
851 | - msgType = DataConstants.ALARM_ACK; | ||
852 | - break; | ||
853 | - case ALARM_CLEAR: | ||
854 | - msgType = DataConstants.ALARM_CLEAR; | ||
855 | - break; | ||
856 | - case ASSIGNED_FROM_TENANT: | ||
857 | - msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; | ||
858 | - break; | ||
859 | - case ASSIGNED_TO_TENANT: | ||
860 | - msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; | ||
861 | - break; | ||
862 | - case PROVISION_SUCCESS: | ||
863 | - msgType = DataConstants.PROVISION_SUCCESS; | ||
864 | - break; | ||
865 | - case PROVISION_FAILURE: | ||
866 | - msgType = DataConstants.PROVISION_FAILURE; | ||
867 | - break; | ||
868 | - case TIMESERIES_UPDATED: | ||
869 | - msgType = DataConstants.TIMESERIES_UPDATED; | ||
870 | - break; | ||
871 | - case TIMESERIES_DELETED: | ||
872 | - msgType = DataConstants.TIMESERIES_DELETED; | ||
873 | - break; | ||
874 | - case ASSIGNED_TO_EDGE: | ||
875 | - msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; | ||
876 | - break; | ||
877 | - case UNASSIGNED_FROM_EDGE: | ||
878 | - msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE; | ||
879 | - break; | ||
880 | - } | ||
881 | - if (!StringUtils.isEmpty(msgType)) { | ||
882 | - try { | ||
883 | - TbMsgMetaData metaData = new TbMsgMetaData(); | ||
884 | - metaData.putValue("userId", user.getId().toString()); | ||
885 | - metaData.putValue("userName", user.getName()); | ||
886 | - if (customerId != null && !customerId.isNullUid()) { | ||
887 | - metaData.putValue("customerId", customerId.toString()); | ||
888 | - } | ||
889 | - if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) { | ||
890 | - String strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
891 | - String strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
892 | - metaData.putValue("assignedCustomerId", strCustomerId); | ||
893 | - metaData.putValue("assignedCustomerName", strCustomerName); | ||
894 | - } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) { | ||
895 | - String strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
896 | - String strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
897 | - metaData.putValue("unassignedCustomerId", strCustomerId); | ||
898 | - metaData.putValue("unassignedCustomerName", strCustomerName); | ||
899 | - } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) { | ||
900 | - String strTenantId = extractParameter(String.class, 0, additionalInfo); | ||
901 | - String strTenantName = extractParameter(String.class, 1, additionalInfo); | ||
902 | - metaData.putValue("assignedFromTenantId", strTenantId); | ||
903 | - metaData.putValue("assignedFromTenantName", strTenantName); | ||
904 | - } else if (actionType == ActionType.ASSIGNED_TO_TENANT) { | ||
905 | - String strTenantId = extractParameter(String.class, 0, additionalInfo); | ||
906 | - String strTenantName = extractParameter(String.class, 1, additionalInfo); | ||
907 | - metaData.putValue("assignedToTenantId", strTenantId); | ||
908 | - metaData.putValue("assignedToTenantName", strTenantName); | ||
909 | - } else if (actionType == ActionType.ASSIGNED_TO_EDGE) { | ||
910 | - String strEdgeId = extractParameter(String.class, 1, additionalInfo); | ||
911 | - String strEdgeName = extractParameter(String.class, 2, additionalInfo); | ||
912 | - metaData.putValue("assignedEdgeId", strEdgeId); | ||
913 | - metaData.putValue("assignedEdgeName", strEdgeName); | ||
914 | - } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { | ||
915 | - String strEdgeId = extractParameter(String.class, 1, additionalInfo); | ||
916 | - String strEdgeName = extractParameter(String.class, 2, additionalInfo); | ||
917 | - metaData.putValue("unassignedEdgeId", strEdgeId); | ||
918 | - metaData.putValue("unassignedEdgeName", strEdgeName); | ||
919 | - } | ||
920 | - ObjectNode entityNode; | ||
921 | - if (entity != null) { | ||
922 | - entityNode = json.valueToTree(entity); | ||
923 | - if (entityId.getEntityType() == EntityType.DASHBOARD) { | ||
924 | - entityNode.put("configuration", ""); | ||
925 | - } | ||
926 | - } else { | ||
927 | - entityNode = json.createObjectNode(); | ||
928 | - if (actionType == ActionType.ATTRIBUTES_UPDATED) { | ||
929 | - String scope = extractParameter(String.class, 0, additionalInfo); | ||
930 | - @SuppressWarnings("unchecked") | ||
931 | - List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); | ||
932 | - metaData.putValue(DataConstants.SCOPE, scope); | ||
933 | - if (attributes != null) { | ||
934 | - for (AttributeKvEntry attr : attributes) { | ||
935 | - addKvEntry(entityNode, attr); | ||
936 | - } | ||
937 | - } | ||
938 | - } else if (actionType == ActionType.ATTRIBUTES_DELETED) { | ||
939 | - String scope = extractParameter(String.class, 0, additionalInfo); | ||
940 | - @SuppressWarnings("unchecked") | ||
941 | - List<String> keys = extractParameter(List.class, 1, additionalInfo); | ||
942 | - metaData.putValue(DataConstants.SCOPE, scope); | ||
943 | - ArrayNode attrsArrayNode = entityNode.putArray("attributes"); | ||
944 | - if (keys != null) { | ||
945 | - keys.forEach(attrsArrayNode::add); | ||
946 | - } | ||
947 | - } else if (actionType == ActionType.TIMESERIES_UPDATED) { | ||
948 | - @SuppressWarnings("unchecked") | ||
949 | - List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo); | ||
950 | - addTimeseries(entityNode, timeseries); | ||
951 | - } else if (actionType == ActionType.TIMESERIES_DELETED) { | ||
952 | - @SuppressWarnings("unchecked") | ||
953 | - List<String> keys = extractParameter(List.class, 0, additionalInfo); | ||
954 | - if (keys != null) { | ||
955 | - ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries"); | ||
956 | - keys.forEach(timeseriesArrayNode::add); | ||
957 | - } | ||
958 | - entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo)); | ||
959 | - entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo)); | ||
960 | - } | ||
961 | - } | ||
962 | - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); | ||
963 | - TenantId tenantId = user.getTenantId(); | ||
964 | - if (tenantId.isNullUid()) { | ||
965 | - if (entity instanceof HasTenantId) { | ||
966 | - tenantId = ((HasTenantId) entity).getTenantId(); | ||
967 | - } | ||
968 | - } | ||
969 | - tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); | ||
970 | - } catch (Exception e) { | ||
971 | - log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); | ||
972 | - } | ||
973 | - } | ||
974 | - } | ||
975 | - | ||
976 | - private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception { | ||
977 | - if (kvEntry.getDataType() == DataType.BOOLEAN) { | ||
978 | - kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
979 | - } else if (kvEntry.getDataType() == DataType.DOUBLE) { | ||
980 | - kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
981 | - } else if (kvEntry.getDataType() == DataType.LONG) { | ||
982 | - kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
983 | - } else if (kvEntry.getDataType() == DataType.JSON) { | ||
984 | - if (kvEntry.getJsonValue().isPresent()) { | ||
985 | - entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get())); | ||
986 | - } | ||
987 | - } else { | ||
988 | - entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); | ||
989 | - } | ||
990 | - } | ||
991 | - | ||
992 | - private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { | ||
993 | - T result = null; | ||
994 | - if (additionalInfo != null && additionalInfo.length > index) { | ||
995 | - Object paramObject = additionalInfo[index]; | ||
996 | - if (clazz.isInstance(paramObject)) { | ||
997 | - result = clazz.cast(paramObject); | ||
998 | - } | ||
999 | - } | ||
1000 | - return result; | ||
1001 | - } | ||
1002 | - | ||
1003 | protected <E extends HasName> String entityToStr(E entity) { | 818 | protected <E extends HasName> String entityToStr(E entity) { |
1004 | try { | 819 | try { |
1005 | return json.writeValueAsString(json.valueToTree(entity)); | 820 | return json.writeValueAsString(json.valueToTree(entity)); |
@@ -1105,23 +920,6 @@ public abstract class BaseController { | @@ -1105,23 +920,6 @@ public abstract class BaseController { | ||
1105 | return result; | 920 | return result; |
1106 | } | 921 | } |
1107 | 922 | ||
1108 | - private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception { | ||
1109 | - if (timeseries != null && !timeseries.isEmpty()) { | ||
1110 | - ArrayNode result = entityNode.putArray("timeseries"); | ||
1111 | - Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream() | ||
1112 | - .collect(Collectors.groupingBy(TsKvEntry::getTs)); | ||
1113 | - for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) { | ||
1114 | - ObjectNode element = json.createObjectNode(); | ||
1115 | - element.put("ts", entry.getKey()); | ||
1116 | - ObjectNode values = element.putObject("values"); | ||
1117 | - for (TsKvEntry tsKvEntry : entry.getValue()) { | ||
1118 | - addKvEntry(values, tsKvEntry); | ||
1119 | - } | ||
1120 | - result.add(element); | ||
1121 | - } | ||
1122 | - } | ||
1123 | - } | ||
1124 | - | ||
1125 | protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { | 923 | protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { |
1126 | String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; | 924 | String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; |
1127 | if (dashboardId != null && !dashboardId.equals("null")) { | 925 | if (dashboardId != null && !dashboardId.equals("null")) { |
@@ -782,15 +782,17 @@ public class DeviceController extends BaseController { | @@ -782,15 +782,17 @@ public class DeviceController extends BaseController { | ||
782 | } | 782 | } |
783 | 783 | ||
784 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 784 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
785 | - @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET) | 785 | + @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) |
786 | @ResponseBody | 786 | @ResponseBody |
787 | - public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, | ||
788 | - @RequestParam String deviceProfileId) throws ThingsboardException { | 787 | + public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, |
788 | + @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { | ||
789 | checkParameter("OtaPackageType", otaPackageType); | 789 | checkParameter("OtaPackageType", otaPackageType); |
790 | checkParameter("DeviceProfileId", deviceProfileId); | 790 | checkParameter("DeviceProfileId", deviceProfileId); |
791 | try { | 791 | try { |
792 | return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( | 792 | return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( |
793 | - getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType)); | 793 | + getTenantId(), |
794 | + new DeviceProfileId(UUID.fromString(deviceProfileId)), | ||
795 | + OtaPackageType.valueOf(otaPackageType)); | ||
794 | } catch (Exception e) { | 796 | } catch (Exception e) { |
795 | throw handleException(e); | 797 | throw handleException(e); |
796 | } | 798 | } |
@@ -17,7 +17,6 @@ package org.thingsboard.server.controller; | @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.databind.ObjectMapper; | 18 | import com.fasterxml.jackson.databind.ObjectMapper; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | -import org.eclipse.leshan.core.SecurityMode; | ||
21 | import org.springframework.security.access.prepost.PreAuthorize; | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
22 | import org.springframework.web.bind.annotation.PathVariable; | 21 | import org.springframework.web.bind.annotation.PathVariable; |
23 | import org.springframework.web.bind.annotation.RequestBody; | 22 | import org.springframework.web.bind.annotation.RequestBody; |
@@ -45,14 +44,11 @@ import java.util.Map; | @@ -45,14 +44,11 @@ import java.util.Map; | ||
45 | public class Lwm2mController extends BaseController { | 44 | public class Lwm2mController extends BaseController { |
46 | 45 | ||
47 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 46 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
48 | - @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) | 47 | + @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) |
49 | @ResponseBody | 48 | @ResponseBody |
50 | - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode, | ||
51 | - @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { | ||
52 | - checkNotNull(strSecurityMode); | 49 | + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("isBootstrapServer") boolean bootstrapServer) throws ThingsboardException { |
53 | try { | 50 | try { |
54 | - SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode); | ||
55 | - return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); | 51 | + return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); |
56 | } catch (Exception e) { | 52 | } catch (Exception e) { |
57 | throw handleException(e); | 53 | throw handleException(e); |
58 | } | 54 | } |
@@ -22,12 +22,15 @@ import org.springframework.security.access.prepost.PreAuthorize; | @@ -22,12 +22,15 @@ import org.springframework.security.access.prepost.PreAuthorize; | ||
22 | import org.springframework.web.bind.annotation.RequestBody; | 22 | import org.springframework.web.bind.annotation.RequestBody; |
23 | import org.springframework.web.bind.annotation.RequestMapping; | 23 | import org.springframework.web.bind.annotation.RequestMapping; |
24 | import org.springframework.web.bind.annotation.RequestMethod; | 24 | import org.springframework.web.bind.annotation.RequestMethod; |
25 | +import org.springframework.web.bind.annotation.RequestParam; | ||
25 | import org.springframework.web.bind.annotation.ResponseBody; | 26 | import org.springframework.web.bind.annotation.ResponseBody; |
26 | import org.springframework.web.bind.annotation.ResponseStatus; | 27 | import org.springframework.web.bind.annotation.ResponseStatus; |
27 | import org.springframework.web.bind.annotation.RestController; | 28 | import org.springframework.web.bind.annotation.RestController; |
29 | +import org.thingsboard.server.common.data.StringUtils; | ||
28 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 30 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
29 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; | 31 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; |
30 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; | 32 | +import org.thingsboard.server.common.data.oauth2.OAuth2Info; |
33 | +import org.thingsboard.server.common.data.oauth2.PlatformType; | ||
31 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | 34 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
32 | import org.thingsboard.server.queue.util.TbCoreComponent; | 35 | import org.thingsboard.server.queue.util.TbCoreComponent; |
33 | import org.thingsboard.server.service.security.permission.Operation; | 36 | import org.thingsboard.server.service.security.permission.Operation; |
@@ -49,7 +52,9 @@ public class OAuth2Controller extends BaseController { | @@ -49,7 +52,9 @@ public class OAuth2Controller extends BaseController { | ||
49 | 52 | ||
50 | @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) | 53 | @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) |
51 | @ResponseBody | 54 | @ResponseBody |
52 | - public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { | 55 | + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request, |
56 | + @RequestParam(required = false) String pkgName, | ||
57 | + @RequestParam(required = false) String platform) throws ThingsboardException { | ||
53 | try { | 58 | try { |
54 | if (log.isDebugEnabled()) { | 59 | if (log.isDebugEnabled()) { |
55 | log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); | 60 | log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); |
@@ -59,7 +64,13 @@ public class OAuth2Controller extends BaseController { | @@ -59,7 +64,13 @@ public class OAuth2Controller extends BaseController { | ||
59 | log.debug("Header: {} {}", header, request.getHeader(header)); | 64 | log.debug("Header: {} {}", header, request.getHeader(header)); |
60 | } | 65 | } |
61 | } | 66 | } |
62 | - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); | 67 | + PlatformType platformType = null; |
68 | + if (StringUtils.isNotEmpty(platform)) { | ||
69 | + try { | ||
70 | + platformType = PlatformType.valueOf(platform); | ||
71 | + } catch (Exception e) {} | ||
72 | + } | ||
73 | + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType); | ||
63 | } catch (Exception e) { | 74 | } catch (Exception e) { |
64 | throw handleException(e); | 75 | throw handleException(e); |
65 | } | 76 | } |
@@ -68,10 +79,10 @@ public class OAuth2Controller extends BaseController { | @@ -68,10 +79,10 @@ public class OAuth2Controller extends BaseController { | ||
68 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | 79 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
69 | @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") | 80 | @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") |
70 | @ResponseBody | 81 | @ResponseBody |
71 | - public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { | 82 | + public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException { |
72 | try { | 83 | try { |
73 | accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); | 84 | accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); |
74 | - return oAuth2Service.findOAuth2Params(); | 85 | + return oAuth2Service.findOAuth2Info(); |
75 | } catch (Exception e) { | 86 | } catch (Exception e) { |
76 | throw handleException(e); | 87 | throw handleException(e); |
77 | } | 88 | } |
@@ -80,11 +91,11 @@ public class OAuth2Controller extends BaseController { | @@ -80,11 +91,11 @@ public class OAuth2Controller extends BaseController { | ||
80 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | 91 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
81 | @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) | 92 | @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) |
82 | @ResponseStatus(value = HttpStatus.OK) | 93 | @ResponseStatus(value = HttpStatus.OK) |
83 | - public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { | 94 | + public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException { |
84 | try { | 95 | try { |
85 | accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); | 96 | accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); |
86 | - oAuth2Service.saveOAuth2Params(oauth2Params); | ||
87 | - return oAuth2Service.findOAuth2Params(); | 97 | + oAuth2Service.saveOAuth2Info(oauth2Info); |
98 | + return oAuth2Service.findOAuth2Info(); | ||
88 | } catch (Exception e) { | 99 | } catch (Exception e) { |
89 | throw handleException(e); | 100 | throw handleException(e); |
90 | } | 101 | } |
@@ -64,6 +64,10 @@ public class OtaPackageController extends BaseController { | @@ -64,6 +64,10 @@ public class OtaPackageController extends BaseController { | ||
64 | OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); | 64 | OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); |
65 | OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ); | 65 | OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ); |
66 | 66 | ||
67 | + if (otaPackage.hasUrl()) { | ||
68 | + return ResponseEntity.badRequest().build(); | ||
69 | + } | ||
70 | + | ||
67 | ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array()); | 71 | ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array()); |
68 | return ResponseEntity.ok() | 72 | return ResponseEntity.ok() |
69 | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName()) | 73 | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName()) |
@@ -124,7 +128,7 @@ public class OtaPackageController extends BaseController { | @@ -124,7 +128,7 @@ public class OtaPackageController extends BaseController { | ||
124 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | 128 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
125 | @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) | 129 | @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) |
126 | @ResponseBody | 130 | @ResponseBody |
127 | - public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, | 131 | + public OtaPackageInfo saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, |
128 | @RequestParam(required = false) String checksum, | 132 | @RequestParam(required = false) String checksum, |
129 | @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, | 133 | @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, |
130 | @RequestBody MultipartFile file) throws ThingsboardException { | 134 | @RequestBody MultipartFile file) throws ThingsboardException { |
@@ -156,7 +160,7 @@ public class OtaPackageController extends BaseController { | @@ -156,7 +160,7 @@ public class OtaPackageController extends BaseController { | ||
156 | otaPackage.setContentType(file.getContentType()); | 160 | otaPackage.setContentType(file.getContentType()); |
157 | otaPackage.setData(ByteBuffer.wrap(bytes)); | 161 | otaPackage.setData(ByteBuffer.wrap(bytes)); |
158 | otaPackage.setDataSize((long) bytes.length); | 162 | otaPackage.setDataSize((long) bytes.length); |
159 | - OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); | 163 | + OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); |
160 | logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null); | 164 | logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null); |
161 | return savedOtaPackage; | 165 | return savedOtaPackage; |
162 | } catch (Exception e) { | 166 | } catch (Exception e) { |
@@ -182,11 +186,10 @@ public class OtaPackageController extends BaseController { | @@ -182,11 +186,10 @@ public class OtaPackageController extends BaseController { | ||
182 | } | 186 | } |
183 | 187 | ||
184 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 188 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
185 | - @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET) | 189 | + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) |
186 | @ResponseBody | 190 | @ResponseBody |
187 | public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId, | 191 | public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId, |
188 | @PathVariable("type") String strType, | 192 | @PathVariable("type") String strType, |
189 | - @PathVariable("hasData") boolean hasData, | ||
190 | @RequestParam int pageSize, | 193 | @RequestParam int pageSize, |
191 | @RequestParam int page, | 194 | @RequestParam int page, |
192 | @RequestParam(required = false) String textSearch, | 195 | @RequestParam(required = false) String textSearch, |
@@ -197,7 +200,7 @@ public class OtaPackageController extends BaseController { | @@ -197,7 +200,7 @@ public class OtaPackageController extends BaseController { | ||
197 | try { | 200 | try { |
198 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | 201 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
199 | return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), | 202 | return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), |
200 | - new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), hasData, pageLink)); | 203 | + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink)); |
201 | } catch (Exception e) { | 204 | } catch (Exception e) { |
202 | throw handleException(e); | 205 | throw handleException(e); |
203 | } | 206 | } |
@@ -75,6 +75,7 @@ import java.util.List; | @@ -75,6 +75,7 @@ import java.util.List; | ||
75 | import java.util.Map; | 75 | import java.util.Map; |
76 | import java.util.Set; | 76 | import java.util.Set; |
77 | import java.util.concurrent.ConcurrentMap; | 77 | import java.util.concurrent.ConcurrentMap; |
78 | +import java.util.concurrent.TimeUnit; | ||
78 | import java.util.stream.Collectors; | 79 | import java.util.stream.Collectors; |
79 | 80 | ||
80 | @Slf4j | 81 | @Slf4j |
@@ -89,6 +90,7 @@ public class RuleChainController extends BaseController { | @@ -89,6 +90,7 @@ public class RuleChainController extends BaseController { | ||
89 | private static final int DEFAULT_PAGE_SIZE = 1000; | 90 | private static final int DEFAULT_PAGE_SIZE = 1000; |
90 | 91 | ||
91 | private static final ObjectMapper objectMapper = new ObjectMapper(); | 92 | private static final ObjectMapper objectMapper = new ObjectMapper(); |
93 | + public static final int TIMEOUT = 20; | ||
92 | 94 | ||
93 | @Autowired | 95 | @Autowired |
94 | private InstallScripts installScripts; | 96 | private InstallScripts installScripts; |
@@ -391,25 +393,25 @@ public class RuleChainController extends BaseController { | @@ -391,25 +393,25 @@ public class RuleChainController extends BaseController { | ||
391 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); | 393 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); |
392 | switch (scriptType) { | 394 | switch (scriptType) { |
393 | case "update": | 395 | case "update": |
394 | - output = msgToOutput(engine.executeUpdate(inMsg)); | 396 | + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); |
395 | break; | 397 | break; |
396 | case "generate": | 398 | case "generate": |
397 | - output = msgToOutput(engine.executeGenerate(inMsg)); | 399 | + output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); |
398 | break; | 400 | break; |
399 | case "filter": | 401 | case "filter": |
400 | - boolean result = engine.executeFilter(inMsg); | 402 | + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
401 | output = Boolean.toString(result); | 403 | output = Boolean.toString(result); |
402 | break; | 404 | break; |
403 | case "switch": | 405 | case "switch": |
404 | - Set<String> states = engine.executeSwitch(inMsg); | 406 | + Set<String> states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
405 | output = objectMapper.writeValueAsString(states); | 407 | output = objectMapper.writeValueAsString(states); |
406 | break; | 408 | break; |
407 | case "json": | 409 | case "json": |
408 | - JsonNode json = engine.executeJson(inMsg); | 410 | + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
409 | output = objectMapper.writeValueAsString(json); | 411 | output = objectMapper.writeValueAsString(json); |
410 | break; | 412 | break; |
411 | case "string": | 413 | case "string": |
412 | - output = engine.executeToString(inMsg); | 414 | + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
413 | break; | 415 | break; |
414 | default: | 416 | default: |
415 | throw new IllegalArgumentException("Unsupported script type: " + scriptType); | 417 | throw new IllegalArgumentException("Unsupported script type: " + scriptType); |
@@ -199,6 +199,7 @@ public class ThingsboardInstallService { | @@ -199,6 +199,7 @@ public class ThingsboardInstallService { | ||
199 | databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); | 199 | databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); |
200 | 200 | ||
201 | dataUpdateService.updateData("3.2.2"); | 201 | dataUpdateService.updateData("3.2.2"); |
202 | + systemDataLoaderService.createOAuth2Templates(); | ||
202 | 203 | ||
203 | log.info("Updating system data..."); | 204 | log.info("Updating system data..."); |
204 | systemDataLoaderService.updateSystemWidgets(); | 205 | systemDataLoaderService.updateSystemWidgets(); |
application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.service.action; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
19 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
21 | +import lombok.RequiredArgsConstructor; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.apache.commons.lang3.StringUtils; | ||
24 | +import org.springframework.stereotype.Service; | ||
25 | +import org.thingsboard.server.common.data.DataConstants; | ||
26 | +import org.thingsboard.server.common.data.EntityType; | ||
27 | +import org.thingsboard.server.common.data.HasName; | ||
28 | +import org.thingsboard.server.common.data.HasTenantId; | ||
29 | +import org.thingsboard.server.common.data.User; | ||
30 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
31 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
32 | +import org.thingsboard.server.common.data.id.EntityId; | ||
33 | +import org.thingsboard.server.common.data.id.TenantId; | ||
34 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
35 | +import org.thingsboard.server.common.data.kv.DataType; | ||
36 | +import org.thingsboard.server.common.data.kv.KvEntry; | ||
37 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
38 | +import org.thingsboard.server.common.msg.TbMsg; | ||
39 | +import org.thingsboard.server.common.msg.TbMsgDataType; | ||
40 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | ||
41 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
42 | +import org.thingsboard.server.service.queue.TbClusterService; | ||
43 | + | ||
44 | +import java.util.List; | ||
45 | +import java.util.Map; | ||
46 | +import java.util.stream.Collectors; | ||
47 | + | ||
48 | +@TbCoreComponent | ||
49 | +@Service | ||
50 | +@RequiredArgsConstructor | ||
51 | +@Slf4j | ||
52 | +public class RuleEngineEntityActionService { | ||
53 | + private final TbClusterService tbClusterService; | ||
54 | + | ||
55 | + private static final ObjectMapper json = new ObjectMapper(); | ||
56 | + | ||
57 | + public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId, | ||
58 | + ActionType actionType, User user, Object... additionalInfo) { | ||
59 | + String msgType = null; | ||
60 | + switch (actionType) { | ||
61 | + case ADDED: | ||
62 | + msgType = DataConstants.ENTITY_CREATED; | ||
63 | + break; | ||
64 | + case DELETED: | ||
65 | + msgType = DataConstants.ENTITY_DELETED; | ||
66 | + break; | ||
67 | + case UPDATED: | ||
68 | + msgType = DataConstants.ENTITY_UPDATED; | ||
69 | + break; | ||
70 | + case ASSIGNED_TO_CUSTOMER: | ||
71 | + msgType = DataConstants.ENTITY_ASSIGNED; | ||
72 | + break; | ||
73 | + case UNASSIGNED_FROM_CUSTOMER: | ||
74 | + msgType = DataConstants.ENTITY_UNASSIGNED; | ||
75 | + break; | ||
76 | + case ATTRIBUTES_UPDATED: | ||
77 | + msgType = DataConstants.ATTRIBUTES_UPDATED; | ||
78 | + break; | ||
79 | + case ATTRIBUTES_DELETED: | ||
80 | + msgType = DataConstants.ATTRIBUTES_DELETED; | ||
81 | + break; | ||
82 | + case ALARM_ACK: | ||
83 | + msgType = DataConstants.ALARM_ACK; | ||
84 | + break; | ||
85 | + case ALARM_CLEAR: | ||
86 | + msgType = DataConstants.ALARM_CLEAR; | ||
87 | + break; | ||
88 | + case ALARM_DELETE: | ||
89 | + msgType = DataConstants.ALARM_DELETE; | ||
90 | + break; | ||
91 | + case ASSIGNED_FROM_TENANT: | ||
92 | + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; | ||
93 | + break; | ||
94 | + case ASSIGNED_TO_TENANT: | ||
95 | + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; | ||
96 | + break; | ||
97 | + case PROVISION_SUCCESS: | ||
98 | + msgType = DataConstants.PROVISION_SUCCESS; | ||
99 | + break; | ||
100 | + case PROVISION_FAILURE: | ||
101 | + msgType = DataConstants.PROVISION_FAILURE; | ||
102 | + break; | ||
103 | + case TIMESERIES_UPDATED: | ||
104 | + msgType = DataConstants.TIMESERIES_UPDATED; | ||
105 | + break; | ||
106 | + case TIMESERIES_DELETED: | ||
107 | + msgType = DataConstants.TIMESERIES_DELETED; | ||
108 | + break; | ||
109 | + case ASSIGNED_TO_EDGE: | ||
110 | + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; | ||
111 | + break; | ||
112 | + case UNASSIGNED_FROM_EDGE: | ||
113 | + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE; | ||
114 | + break; | ||
115 | + } | ||
116 | + if (!StringUtils.isEmpty(msgType)) { | ||
117 | + try { | ||
118 | + TbMsgMetaData metaData = new TbMsgMetaData(); | ||
119 | + if (user != null) { | ||
120 | + metaData.putValue("userId", user.getId().toString()); | ||
121 | + metaData.putValue("userName", user.getName()); | ||
122 | + } | ||
123 | + if (customerId != null && !customerId.isNullUid()) { | ||
124 | + metaData.putValue("customerId", customerId.toString()); | ||
125 | + } | ||
126 | + if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) { | ||
127 | + String strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
128 | + String strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
129 | + metaData.putValue("assignedCustomerId", strCustomerId); | ||
130 | + metaData.putValue("assignedCustomerName", strCustomerName); | ||
131 | + } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) { | ||
132 | + String strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
133 | + String strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
134 | + metaData.putValue("unassignedCustomerId", strCustomerId); | ||
135 | + metaData.putValue("unassignedCustomerName", strCustomerName); | ||
136 | + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) { | ||
137 | + String strTenantId = extractParameter(String.class, 0, additionalInfo); | ||
138 | + String strTenantName = extractParameter(String.class, 1, additionalInfo); | ||
139 | + metaData.putValue("assignedFromTenantId", strTenantId); | ||
140 | + metaData.putValue("assignedFromTenantName", strTenantName); | ||
141 | + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) { | ||
142 | + String strTenantId = extractParameter(String.class, 0, additionalInfo); | ||
143 | + String strTenantName = extractParameter(String.class, 1, additionalInfo); | ||
144 | + metaData.putValue("assignedToTenantId", strTenantId); | ||
145 | + metaData.putValue("assignedToTenantName", strTenantName); | ||
146 | + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) { | ||
147 | + String strEdgeId = extractParameter(String.class, 1, additionalInfo); | ||
148 | + String strEdgeName = extractParameter(String.class, 2, additionalInfo); | ||
149 | + metaData.putValue("assignedEdgeId", strEdgeId); | ||
150 | + metaData.putValue("assignedEdgeName", strEdgeName); | ||
151 | + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { | ||
152 | + String strEdgeId = extractParameter(String.class, 1, additionalInfo); | ||
153 | + String strEdgeName = extractParameter(String.class, 2, additionalInfo); | ||
154 | + metaData.putValue("unassignedEdgeId", strEdgeId); | ||
155 | + metaData.putValue("unassignedEdgeName", strEdgeName); | ||
156 | + } | ||
157 | + ObjectNode entityNode; | ||
158 | + if (entity != null) { | ||
159 | + entityNode = json.valueToTree(entity); | ||
160 | + if (entityId.getEntityType() == EntityType.DASHBOARD) { | ||
161 | + entityNode.put("configuration", ""); | ||
162 | + } | ||
163 | + } else { | ||
164 | + entityNode = json.createObjectNode(); | ||
165 | + if (actionType == ActionType.ATTRIBUTES_UPDATED) { | ||
166 | + String scope = extractParameter(String.class, 0, additionalInfo); | ||
167 | + @SuppressWarnings("unchecked") | ||
168 | + List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); | ||
169 | + metaData.putValue(DataConstants.SCOPE, scope); | ||
170 | + if (attributes != null) { | ||
171 | + for (AttributeKvEntry attr : attributes) { | ||
172 | + addKvEntry(entityNode, attr); | ||
173 | + } | ||
174 | + } | ||
175 | + } else if (actionType == ActionType.ATTRIBUTES_DELETED) { | ||
176 | + String scope = extractParameter(String.class, 0, additionalInfo); | ||
177 | + @SuppressWarnings("unchecked") | ||
178 | + List<String> keys = extractParameter(List.class, 1, additionalInfo); | ||
179 | + metaData.putValue(DataConstants.SCOPE, scope); | ||
180 | + ArrayNode attrsArrayNode = entityNode.putArray("attributes"); | ||
181 | + if (keys != null) { | ||
182 | + keys.forEach(attrsArrayNode::add); | ||
183 | + } | ||
184 | + } else if (actionType == ActionType.TIMESERIES_UPDATED) { | ||
185 | + @SuppressWarnings("unchecked") | ||
186 | + List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo); | ||
187 | + addTimeseries(entityNode, timeseries); | ||
188 | + } else if (actionType == ActionType.TIMESERIES_DELETED) { | ||
189 | + @SuppressWarnings("unchecked") | ||
190 | + List<String> keys = extractParameter(List.class, 0, additionalInfo); | ||
191 | + if (keys != null) { | ||
192 | + ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries"); | ||
193 | + keys.forEach(timeseriesArrayNode::add); | ||
194 | + } | ||
195 | + entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo)); | ||
196 | + entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo)); | ||
197 | + } | ||
198 | + } | ||
199 | + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); | ||
200 | + if (tenantId.isNullUid()) { | ||
201 | + if (entity instanceof HasTenantId) { | ||
202 | + tenantId = ((HasTenantId) entity).getTenantId(); | ||
203 | + } | ||
204 | + } | ||
205 | + tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); | ||
206 | + } catch (Exception e) { | ||
207 | + log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); | ||
208 | + } | ||
209 | + } | ||
210 | + } | ||
211 | + | ||
212 | + | ||
213 | + private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { | ||
214 | + T result = null; | ||
215 | + if (additionalInfo != null && additionalInfo.length > index) { | ||
216 | + Object paramObject = additionalInfo[index]; | ||
217 | + if (clazz.isInstance(paramObject)) { | ||
218 | + result = clazz.cast(paramObject); | ||
219 | + } | ||
220 | + } | ||
221 | + return result; | ||
222 | + } | ||
223 | + | ||
224 | + private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception { | ||
225 | + if (timeseries != null && !timeseries.isEmpty()) { | ||
226 | + ArrayNode result = entityNode.putArray("timeseries"); | ||
227 | + Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream() | ||
228 | + .collect(Collectors.groupingBy(TsKvEntry::getTs)); | ||
229 | + for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) { | ||
230 | + ObjectNode element = json.createObjectNode(); | ||
231 | + element.put("ts", entry.getKey()); | ||
232 | + ObjectNode values = element.putObject("values"); | ||
233 | + for (TsKvEntry tsKvEntry : entry.getValue()) { | ||
234 | + addKvEntry(values, tsKvEntry); | ||
235 | + } | ||
236 | + result.add(element); | ||
237 | + } | ||
238 | + } | ||
239 | + } | ||
240 | + | ||
241 | + private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception { | ||
242 | + if (kvEntry.getDataType() == DataType.BOOLEAN) { | ||
243 | + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
244 | + } else if (kvEntry.getDataType() == DataType.DOUBLE) { | ||
245 | + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
246 | + } else if (kvEntry.getDataType() == DataType.LONG) { | ||
247 | + kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); | ||
248 | + } else if (kvEntry.getDataType() == DataType.JSON) { | ||
249 | + if (kvEntry.getJsonValue().isPresent()) { | ||
250 | + entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get())); | ||
251 | + } | ||
252 | + } else { | ||
253 | + entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); | ||
254 | + } | ||
255 | + } | ||
256 | +} |
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; | ||
23 | import org.springframework.beans.factory.annotation.Autowired; | 23 | import org.springframework.beans.factory.annotation.Autowired; |
24 | import org.springframework.context.annotation.Profile; | 24 | import org.springframework.context.annotation.Profile; |
25 | import org.springframework.stereotype.Service; | 25 | import org.springframework.stereotype.Service; |
26 | +import org.thingsboard.common.util.JacksonUtil; | ||
26 | import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; | 27 | import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; |
27 | import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; | 28 | import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; |
28 | import org.thingsboard.server.common.data.EntityView; | 29 | import org.thingsboard.server.common.data.EntityView; |
@@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId; | @@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId; | ||
35 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | 36 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
36 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | 37 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
37 | import org.thingsboard.server.common.data.kv.TsKvEntry; | 38 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
39 | +import org.thingsboard.server.common.data.oauth2.OAuth2Info; | ||
40 | +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; | ||
38 | import org.thingsboard.server.common.data.page.PageData; | 41 | import org.thingsboard.server.common.data.page.PageData; |
39 | import org.thingsboard.server.common.data.page.PageLink; | 42 | import org.thingsboard.server.common.data.page.PageLink; |
40 | import org.thingsboard.server.common.data.page.TimePageLink; | 43 | import org.thingsboard.server.common.data.page.TimePageLink; |
@@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao; | @@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao; | ||
45 | import org.thingsboard.server.dao.alarm.AlarmService; | 48 | import org.thingsboard.server.dao.alarm.AlarmService; |
46 | import org.thingsboard.server.dao.entity.EntityService; | 49 | import org.thingsboard.server.dao.entity.EntityService; |
47 | import org.thingsboard.server.dao.entityview.EntityViewService; | 50 | import org.thingsboard.server.dao.entityview.EntityViewService; |
51 | +import org.thingsboard.server.dao.oauth2.OAuth2Service; | ||
52 | +import org.thingsboard.server.dao.oauth2.OAuth2Utils; | ||
48 | import org.thingsboard.server.dao.rule.RuleChainService; | 53 | import org.thingsboard.server.dao.rule.RuleChainService; |
49 | import org.thingsboard.server.dao.tenant.TenantService; | 54 | import org.thingsboard.server.dao.tenant.TenantService; |
50 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 55 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
51 | -import org.thingsboard.common.util.JacksonUtil; | ||
52 | import org.thingsboard.server.service.install.InstallScripts; | 56 | import org.thingsboard.server.service.install.InstallScripts; |
53 | 57 | ||
54 | import java.util.ArrayList; | 58 | import java.util.ArrayList; |
@@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService { | @@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService { | ||
88 | @Autowired | 92 | @Autowired |
89 | private AlarmDao alarmDao; | 93 | private AlarmDao alarmDao; |
90 | 94 | ||
95 | + @Autowired | ||
96 | + private OAuth2Service oAuth2Service; | ||
97 | + | ||
91 | @Override | 98 | @Override |
92 | public void updateData(String fromVersion) throws Exception { | 99 | public void updateData(String fromVersion) throws Exception { |
93 | switch (fromVersion) { | 100 | switch (fromVersion) { |
@@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService { | @@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService { | ||
107 | log.info("Updating data from version 3.2.2 to 3.3.0 ..."); | 114 | log.info("Updating data from version 3.2.2 to 3.3.0 ..."); |
108 | tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); | 115 | tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); |
109 | tenantsAlarmsCustomerUpdater.updateEntities(null); | 116 | tenantsAlarmsCustomerUpdater.updateEntities(null); |
117 | + updateOAuth2Params(); | ||
110 | break; | 118 | break; |
111 | default: | 119 | default: |
112 | throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); | 120 | throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); |
@@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService { | @@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService { | ||
362 | } | 370 | } |
363 | } | 371 | } |
364 | 372 | ||
373 | + private void updateOAuth2Params() { | ||
374 | + try { | ||
375 | + OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params(); | ||
376 | + if (!oauth2ClientsParams.getDomainsParams().isEmpty()) { | ||
377 | + log.info("Updating OAuth2 parameters ..."); | ||
378 | + OAuth2Info oAuth2Info = OAuth2Utils.clientParamsToOAuth2Info(oauth2ClientsParams); | ||
379 | + oAuth2Service.saveOAuth2Info(oAuth2Info); | ||
380 | + oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList())); | ||
381 | + log.info("Successfully updated OAuth2 parameters!"); | ||
382 | + } | ||
383 | + } | ||
384 | + catch (Exception e) { | ||
385 | + log.error("Failed to update OAuth2 parameters", e); | ||
386 | + } | ||
387 | + } | ||
388 | + | ||
365 | } | 389 | } |
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.lwm2m; | @@ -18,7 +18,6 @@ package org.thingsboard.server.service.lwm2m; | ||
18 | 18 | ||
19 | import lombok.RequiredArgsConstructor; | 19 | import lombok.RequiredArgsConstructor; |
20 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
21 | -import org.eclipse.leshan.core.SecurityMode; | ||
22 | import org.eclipse.leshan.core.util.Hex; | 21 | import org.eclipse.leshan.core.util.Hex; |
23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
24 | import org.springframework.stereotype.Service; | 23 | import org.springframework.stereotype.Service; |
@@ -50,40 +49,20 @@ public class LwM2MServerSecurityInfoRepository { | @@ -50,40 +49,20 @@ public class LwM2MServerSecurityInfoRepository { | ||
50 | private final LwM2MTransportServerConfig serverConfig; | 49 | private final LwM2MTransportServerConfig serverConfig; |
51 | private final LwM2MTransportBootstrapConfig bootstrapConfig; | 50 | private final LwM2MTransportBootstrapConfig bootstrapConfig; |
52 | 51 | ||
53 | - /** | ||
54 | - * @param securityMode | ||
55 | - * @param bootstrapServer | ||
56 | - * @return ServerSecurityConfig more value is default: Important - port, host, publicKey | ||
57 | - */ | ||
58 | - public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) { | ||
59 | - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode); | 52 | + public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { |
53 | + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); | ||
60 | result.setBootstrapServerIs(bootstrapServer); | 54 | result.setBootstrapServerIs(bootstrapServer); |
61 | return result; | 55 | return result; |
62 | } | 56 | } |
63 | 57 | ||
64 | - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) { | 58 | + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { |
65 | ServerSecurityConfig bsServ = new ServerSecurityConfig(); | 59 | ServerSecurityConfig bsServ = new ServerSecurityConfig(); |
66 | bsServ.setServerId(serverConfig.getId()); | 60 | bsServ.setServerId(serverConfig.getId()); |
67 | - switch (securityMode) { | ||
68 | - case NO_SEC: | ||
69 | - bsServ.setHost(serverConfig.getHost()); | ||
70 | - bsServ.setPort(serverConfig.getPort()); | ||
71 | - bsServ.setServerPublicKey(""); | ||
72 | - break; | ||
73 | - case PSK: | ||
74 | - bsServ.setHost(serverConfig.getSecureHost()); | ||
75 | - bsServ.setPort(serverConfig.getSecurePort()); | ||
76 | - bsServ.setServerPublicKey(""); | ||
77 | - break; | ||
78 | - case RPK: | ||
79 | - case X509: | ||
80 | - bsServ.setHost(serverConfig.getSecureHost()); | ||
81 | - bsServ.setPort(serverConfig.getSecurePort()); | ||
82 | - bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); | ||
83 | - break; | ||
84 | - default: | ||
85 | - break; | ||
86 | - } | 61 | + bsServ.setHost(serverConfig.getHost()); |
62 | + bsServ.setPort(serverConfig.getPort()); | ||
63 | + bsServ.setSecurityHost(serverConfig.getSecureHost()); | ||
64 | + bsServ.setSecurityPort(serverConfig.getSecurePort()); | ||
65 | + bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); | ||
87 | return bsServ; | 66 | return bsServ; |
88 | } | 67 | } |
89 | 68 |
@@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.DataConstants; | @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.DataConstants; | ||
24 | import org.thingsboard.server.common.data.Device; | 24 | import org.thingsboard.server.common.data.Device; |
25 | import org.thingsboard.server.common.data.DeviceProfile; | 25 | import org.thingsboard.server.common.data.DeviceProfile; |
26 | import org.thingsboard.server.common.data.OtaPackageInfo; | 26 | import org.thingsboard.server.common.data.OtaPackageInfo; |
27 | +import org.thingsboard.server.common.data.StringUtils; | ||
27 | import org.thingsboard.server.common.data.id.DeviceId; | 28 | import org.thingsboard.server.common.data.id.DeviceId; |
28 | import org.thingsboard.server.common.data.id.OtaPackageId; | 29 | import org.thingsboard.server.common.data.id.OtaPackageId; |
29 | import org.thingsboard.server.common.data.id.TenantId; | 30 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -65,6 +66,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE; | @@ -65,6 +66,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE; | ||
65 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; | 66 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; |
66 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE; | 67 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE; |
67 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS; | 68 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS; |
69 | +import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL; | ||
68 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION; | 70 | import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION; |
69 | import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; | 71 | import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; |
70 | import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; | 72 | import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; |
@@ -261,11 +263,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | @@ -261,11 +263,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | ||
261 | } | 263 | } |
262 | 264 | ||
263 | 265 | ||
264 | - private void update(Device device, OtaPackageInfo firmware, long ts) { | 266 | + private void update(Device device, OtaPackageInfo otaPackage, long ts) { |
265 | TenantId tenantId = device.getTenantId(); | 267 | TenantId tenantId = device.getTenantId(); |
266 | DeviceId deviceId = device.getId(); | 268 | DeviceId deviceId = device.getId(); |
269 | + OtaPackageType otaPackageType = otaPackage.getType(); | ||
267 | 270 | ||
268 | - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.INITIATED.name())); | 271 | + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name())); |
269 | 272 | ||
270 | telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { | 273 | telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { |
271 | @Override | 274 | @Override |
@@ -280,11 +283,37 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | @@ -280,11 +283,37 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | ||
280 | }); | 283 | }); |
281 | 284 | ||
282 | List<AttributeKvEntry> attributes = new ArrayList<>(); | 285 | List<AttributeKvEntry> attributes = new ArrayList<>(); |
283 | - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle()))); | ||
284 | - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion()))); | ||
285 | - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize()))); | ||
286 | - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name()))); | ||
287 | - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum()))); | 286 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); |
287 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion()))); | ||
288 | + if (otaPackage.hasUrl()) { | ||
289 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); | ||
290 | + List<String> attrToRemove = new ArrayList<>(); | ||
291 | + | ||
292 | + if (otaPackage.getDataSize() == null) { | ||
293 | + attrToRemove.add(getAttributeKey(otaPackageType, SIZE)); | ||
294 | + } else { | ||
295 | + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize()))); | ||
296 | + } | ||
297 | + | ||
298 | + if (otaPackage.getChecksumAlgorithm() != null) { | ||
299 | + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM)); | ||
300 | + } else { | ||
301 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name()))); | ||
302 | + } | ||
303 | + | ||
304 | + if (StringUtils.isEmpty(otaPackage.getChecksum())) { | ||
305 | + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM)); | ||
306 | + } else { | ||
307 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); | ||
308 | + } | ||
309 | + | ||
310 | + remove(device, otaPackageType, attrToRemove); | ||
311 | + } else { | ||
312 | + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize()))); | ||
313 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name()))); | ||
314 | + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); | ||
315 | + remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL))); | ||
316 | + } | ||
288 | 317 | ||
289 | telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { | 318 | telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { |
290 | @Override | 319 | @Override |
@@ -299,20 +328,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | @@ -299,20 +328,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { | ||
299 | }); | 328 | }); |
300 | } | 329 | } |
301 | 330 | ||
302 | - private void remove(Device device, OtaPackageType firmwareType) { | ||
303 | - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, OtaPackageUtil.getAttributeKeys(firmwareType), | 331 | + private void remove(Device device, OtaPackageType otaPackageType) { |
332 | + remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType)); | ||
333 | + } | ||
334 | + | ||
335 | + private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) { | ||
336 | + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys, | ||
304 | new FutureCallback<>() { | 337 | new FutureCallback<>() { |
305 | @Override | 338 | @Override |
306 | public void onSuccess(@Nullable Void tmp) { | 339 | public void onSuccess(@Nullable Void tmp) { |
307 | - log.trace("[{}] Success remove target firmware attributes!", device.getId()); | 340 | + log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType); |
308 | Set<AttributeKey> keysToNotify = new HashSet<>(); | 341 | Set<AttributeKey> keysToNotify = new HashSet<>(); |
309 | - OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); | 342 | + attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); |
310 | tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); | 343 | tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); |
311 | } | 344 | } |
312 | 345 | ||
313 | @Override | 346 | @Override |
314 | public void onFailure(Throwable t) { | 347 | public void onFailure(Throwable t) { |
315 | - log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t); | 348 | + log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t); |
316 | } | 349 | } |
317 | }); | 350 | }); |
318 | } | 351 | } |
@@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService { | ||
72 | if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { | 72 | if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { |
73 | try { | 73 | try { |
74 | List<ObjectModel> objectModels = | 74 | List<ObjectModel> objectModels = |
75 | - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); | 75 | + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); |
76 | if (!objectModels.isEmpty()) { | 76 | if (!objectModels.isEmpty()) { |
77 | ObjectModel objectModel = objectModels.get(0); | 77 | ObjectModel objectModel = objectModels.get(0); |
78 | 78 | ||
79 | - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion(); | 79 | + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; |
80 | String name = objectModel.name; | 80 | String name = objectModel.name; |
81 | resource.setResourceKey(resourceKey); | 81 | resource.setResourceKey(resourceKey); |
82 | if (resource.getId() == null) { | 82 | if (resource.getId() == null) { |
83 | - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion()); | 83 | + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); |
84 | } | 84 | } |
85 | resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); | 85 | resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); |
86 | } else { | 86 | } else { |
@@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService { | ||
157 | resourceService.deleteResourcesByTenantId(tenantId); | 157 | resourceService.deleteResourcesByTenantId(tenantId); |
158 | } | 158 | } |
159 | 159 | ||
160 | + @Override | ||
161 | + public long sumDataSizeByTenantId(TenantId tenantId) { | ||
162 | + return resourceService.sumDataSizeByTenantId(tenantId); | ||
163 | + } | ||
164 | + | ||
160 | private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) { | 165 | private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) { |
161 | Comparator<LwM2mObject> comparator; | 166 | Comparator<LwM2mObject> comparator; |
162 | if ("name".equals(sortProperty)) { | 167 | if ("name".equals(sortProperty)) { |
@@ -171,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -171,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService { | ||
171 | try { | 176 | try { |
172 | DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); | 177 | DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); |
173 | List<ObjectModel> objectModels = | 178 | List<ObjectModel> objectModels = |
174 | - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); | 179 | + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); |
175 | if (objectModels.size() == 0) { | 180 | if (objectModels.size() == 0) { |
176 | return null; | 181 | return null; |
177 | } else { | 182 | } else { |
@@ -30,6 +30,7 @@ import java.util.UUID; | @@ -30,6 +30,7 @@ import java.util.UUID; | ||
30 | import java.util.concurrent.ConcurrentHashMap; | 30 | import java.util.concurrent.ConcurrentHashMap; |
31 | import java.util.concurrent.Executors; | 31 | import java.util.concurrent.Executors; |
32 | import java.util.concurrent.ScheduledExecutorService; | 32 | import java.util.concurrent.ScheduledExecutorService; |
33 | +import java.util.concurrent.TimeoutException; | ||
33 | import java.util.concurrent.atomic.AtomicInteger; | 34 | import java.util.concurrent.atomic.AtomicInteger; |
34 | 35 | ||
35 | /** | 36 | /** |
@@ -84,8 +85,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { | @@ -84,8 +85,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { | ||
84 | apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); | 85 | apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); |
85 | return doInvokeFunction(scriptId, functionName, args); | 86 | return doInvokeFunction(scriptId, functionName, args); |
86 | } else { | 87 | } else { |
87 | - return Futures.immediateFailedFuture( | ||
88 | - new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!")); | 88 | + String message = "Script invocation is blocked due to maximum error count " |
89 | + + getMaxErrors() + ", scriptId " + scriptId + "!"; | ||
90 | + log.warn(message); | ||
91 | + return Futures.immediateFailedFuture(new RuntimeException(message)); | ||
89 | } | 92 | } |
90 | } else { | 93 | } else { |
91 | return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); | 94 | return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); |
@@ -117,8 +120,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { | @@ -117,8 +120,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { | ||
117 | 120 | ||
118 | protected abstract long getMaxBlacklistDuration(); | 121 | protected abstract long getMaxBlacklistDuration(); |
119 | 122 | ||
120 | - protected void onScriptExecutionError(UUID scriptId) { | ||
121 | - disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); | 123 | + protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) { |
124 | + DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); | ||
125 | + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", | ||
126 | + disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); | ||
127 | + disableListInfo.incrementAndGet(); | ||
122 | } | 128 | } |
123 | 129 | ||
124 | private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { | 130 | private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { |
@@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer | @@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer | ||
160 | return ((Invocable) engine).invokeFunction(functionName, args); | 160 | return ((Invocable) engine).invokeFunction(functionName, args); |
161 | } | 161 | } |
162 | } catch (Exception e) { | 162 | } catch (Exception e) { |
163 | - onScriptExecutionError(scriptId); | 163 | + onScriptExecutionError(scriptId, e, functionName); |
164 | throw new ExecutionException(e); | 164 | throw new ExecutionException(e); |
165 | } | 165 | } |
166 | }); | 166 | }); |
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; | @@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; | ||
18 | import com.google.common.util.concurrent.FutureCallback; | 18 | import com.google.common.util.concurrent.FutureCallback; |
19 | import com.google.common.util.concurrent.Futures; | 19 | import com.google.common.util.concurrent.Futures; |
20 | import com.google.common.util.concurrent.ListenableFuture; | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | -import com.google.common.util.concurrent.MoreExecutors; | ||
22 | import lombok.Getter; | 21 | import lombok.Getter; |
23 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
24 | import org.springframework.beans.factory.annotation.Autowired; | 23 | import org.springframework.beans.factory.annotation.Autowired; |
@@ -26,6 +25,7 @@ import org.springframework.beans.factory.annotation.Value; | @@ -26,6 +25,7 @@ import org.springframework.beans.factory.annotation.Value; | ||
26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
27 | import org.springframework.scheduling.annotation.Scheduled; | 26 | import org.springframework.scheduling.annotation.Scheduled; |
28 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
28 | +import org.springframework.util.StopWatch; | ||
29 | import org.thingsboard.common.util.ThingsBoardThreadFactory; | 29 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
30 | import org.thingsboard.server.gen.js.JsInvokeProtos; | 30 | import org.thingsboard.server.gen.js.JsInvokeProtos; |
31 | import org.thingsboard.server.queue.TbQueueRequestTemplate; | 31 | import org.thingsboard.server.queue.TbQueueRequestTemplate; |
@@ -161,7 +161,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -161,7 +161,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
161 | 161 | ||
162 | @Override | 162 | @Override |
163 | protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { | 163 | protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { |
164 | - String scriptBody = scriptIdToBodysMap.get(scriptId); | 164 | + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout); |
165 | + final String scriptBody = scriptIdToBodysMap.get(scriptId); | ||
165 | if (scriptBody == null) { | 166 | if (scriptBody == null) { |
166 | return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); | 167 | return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); |
167 | } | 168 | } |
@@ -170,7 +171,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -170,7 +171,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
170 | .setScriptIdLSB(scriptId.getLeastSignificantBits()) | 171 | .setScriptIdLSB(scriptId.getLeastSignificantBits()) |
171 | .setFunctionName(functionName) | 172 | .setFunctionName(functionName) |
172 | .setTimeout((int) maxRequestsTimeout) | 173 | .setTimeout((int) maxRequestsTimeout) |
173 | - .setScriptBody(scriptIdToBodysMap.get(scriptId)); | 174 | + .setScriptBody(scriptBody); |
174 | 175 | ||
175 | for (Object arg : args) { | 176 | for (Object arg : args) { |
176 | jsRequestBuilder.addArgs(arg.toString()); | 177 | jsRequestBuilder.addArgs(arg.toString()); |
@@ -180,6 +181,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -180,6 +181,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
180 | .setInvokeRequest(jsRequestBuilder.build()) | 181 | .setInvokeRequest(jsRequestBuilder.build()) |
181 | .build(); | 182 | .build(); |
182 | 183 | ||
184 | + StopWatch stopWatch = new StopWatch(); | ||
185 | + stopWatch.start(); | ||
186 | + | ||
183 | ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); | 187 | ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); |
184 | if (maxRequestsTimeout > 0) { | 188 | if (maxRequestsTimeout > 0) { |
185 | future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | 189 | future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); |
@@ -193,7 +197,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -193,7 +197,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
193 | 197 | ||
194 | @Override | 198 | @Override |
195 | public void onFailure(Throwable t) { | 199 | public void onFailure(Throwable t) { |
196 | - onScriptExecutionError(scriptId); | 200 | + onScriptExecutionError(scriptId, t, scriptBody); |
197 | if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { | 201 | if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { |
198 | queueTimeoutMsgs.incrementAndGet(); | 202 | queueTimeoutMsgs.incrementAndGet(); |
199 | } | 203 | } |
@@ -201,13 +205,16 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -201,13 +205,16 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
201 | } | 205 | } |
202 | }, callbackExecutor); | 206 | }, callbackExecutor); |
203 | return Futures.transform(future, response -> { | 207 | return Futures.transform(future, response -> { |
208 | + stopWatch.stop(); | ||
209 | + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); | ||
204 | JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); | 210 | JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); |
205 | if (invokeResult.getSuccess()) { | 211 | if (invokeResult.getSuccess()) { |
206 | return invokeResult.getResult(); | 212 | return invokeResult.getResult(); |
207 | } else { | 213 | } else { |
208 | - onScriptExecutionError(scriptId); | 214 | + final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); |
215 | + onScriptExecutionError(scriptId, e, scriptBody); | ||
209 | log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); | 216 | log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); |
210 | - throw new RuntimeException(invokeResult.getErrorDetails()); | 217 | + throw e; |
211 | } | 218 | } |
212 | }, callbackExecutor); | 219 | }, callbackExecutor); |
213 | } | 220 | } |
@@ -18,12 +18,12 @@ package org.thingsboard.server.service.script; | @@ -18,12 +18,12 @@ package org.thingsboard.server.service.script; | ||
18 | import com.fasterxml.jackson.core.type.TypeReference; | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | import com.fasterxml.jackson.databind.JsonNode; | 19 | import com.fasterxml.jackson.databind.JsonNode; |
20 | import com.fasterxml.jackson.databind.ObjectMapper; | 20 | import com.fasterxml.jackson.databind.ObjectMapper; |
21 | -import com.google.common.collect.Sets; | ||
22 | import com.google.common.util.concurrent.Futures; | 21 | import com.google.common.util.concurrent.Futures; |
23 | import com.google.common.util.concurrent.ListenableFuture; | 22 | import com.google.common.util.concurrent.ListenableFuture; |
24 | import com.google.common.util.concurrent.MoreExecutors; | 23 | import com.google.common.util.concurrent.MoreExecutors; |
25 | import lombok.extern.slf4j.Slf4j; | 24 | import lombok.extern.slf4j.Slf4j; |
26 | import org.apache.commons.lang3.StringUtils; | 25 | import org.apache.commons.lang3.StringUtils; |
26 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
27 | import org.thingsboard.server.common.data.id.EntityId; | 27 | import org.thingsboard.server.common.data.id.EntityId; |
28 | import org.thingsboard.server.common.data.id.TenantId; | 28 | import org.thingsboard.server.common.data.id.TenantId; |
29 | import org.thingsboard.server.common.msg.TbMsg; | 29 | import org.thingsboard.server.common.msg.TbMsg; |
@@ -32,6 +32,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; | @@ -32,6 +32,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; | ||
32 | import javax.script.ScriptException; | 32 | import javax.script.ScriptException; |
33 | import java.util.ArrayList; | 33 | import java.util.ArrayList; |
34 | import java.util.Collections; | 34 | import java.util.Collections; |
35 | +import java.util.HashSet; | ||
35 | import java.util.List; | 36 | import java.util.List; |
36 | import java.util.Map; | 37 | import java.util.Map; |
37 | import java.util.Set; | 38 | import java.util.Set; |
@@ -102,140 +103,115 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | @@ -102,140 +103,115 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | ||
102 | String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); | 103 | String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); |
103 | return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); | 104 | return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); |
104 | } catch (Throwable th) { | 105 | } catch (Throwable th) { |
105 | - th.printStackTrace(); | ||
106 | throw new RuntimeException("Failed to unbind message data from javascript result", th); | 106 | throw new RuntimeException("Failed to unbind message data from javascript result", th); |
107 | } | 107 | } |
108 | } | 108 | } |
109 | 109 | ||
110 | @Override | 110 | @Override |
111 | - public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException { | ||
112 | - JsonNode result = executeScript(msg); | ||
113 | - if (result.isObject()) { | ||
114 | - return Collections.singletonList(unbindMsg(result, msg)); | ||
115 | - } else if (result.isArray()){ | ||
116 | - List<TbMsg> res = new ArrayList<>(result.size()); | ||
117 | - result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
118 | - return res; | ||
119 | - } else { | ||
120 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
121 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | 111 | + public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { |
112 | + ListenableFuture<JsonNode> result = executeScriptAsync(msg); | ||
113 | + return Futures.transformAsync(result, | ||
114 | + json -> executeUpdateTransform(msg, json), | ||
115 | + MoreExecutors.directExecutor()); | ||
116 | + } | ||
117 | + | ||
118 | + ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) { | ||
119 | + if (json.isObject()) { | ||
120 | + return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | ||
121 | + } else if (json.isArray()) { | ||
122 | + List<TbMsg> res = new ArrayList<>(json.size()); | ||
123 | + json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
124 | + return Futures.immediateFuture(res); | ||
122 | } | 125 | } |
126 | + log.warn("Wrong result type: {}", json.getNodeType()); | ||
127 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
123 | } | 128 | } |
124 | 129 | ||
125 | @Override | 130 | @Override |
126 | - public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { | ||
127 | - ListenableFuture<JsonNode> result = executeScriptAsync(msg); | ||
128 | - return Futures.transformAsync(result, json -> { | ||
129 | - if (json.isObject()) { | ||
130 | - return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | ||
131 | - } else if (json.isArray()){ | ||
132 | - List<TbMsg> res = new ArrayList<>(json.size()); | ||
133 | - json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
134 | - return Futures.immediateFuture(res); | ||
135 | - } | ||
136 | - else{ | ||
137 | - log.warn("Wrong result type: {}", json.getNodeType()); | ||
138 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
139 | - } | ||
140 | - }, MoreExecutors.directExecutor()); | 131 | + public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { |
132 | + return Futures.transformAsync(executeScriptAsync(prevMsg), | ||
133 | + result -> executeGenerateTransform(prevMsg, result), | ||
134 | + MoreExecutors.directExecutor()); | ||
141 | } | 135 | } |
142 | 136 | ||
143 | - @Override | ||
144 | - public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException { | ||
145 | - JsonNode result = executeScript(prevMsg); | 137 | + ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) { |
146 | if (!result.isObject()) { | 138 | if (!result.isObject()) { |
147 | log.warn("Wrong result type: {}", result.getNodeType()); | 139 | log.warn("Wrong result type: {}", result.getNodeType()); |
148 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | 140 | + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); |
149 | } | 141 | } |
150 | - return unbindMsg(result, prevMsg); | 142 | + return Futures.immediateFuture(unbindMsg(result, prevMsg)); |
151 | } | 143 | } |
152 | 144 | ||
153 | @Override | 145 | @Override |
154 | - public JsonNode executeJson(TbMsg msg) throws ScriptException { | ||
155 | - return executeScript(msg); | ||
156 | - } | ||
157 | - | ||
158 | - @Override | ||
159 | - public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException { | 146 | + public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) { |
160 | return executeScriptAsync(msg); | 147 | return executeScriptAsync(msg); |
161 | } | 148 | } |
162 | 149 | ||
163 | @Override | 150 | @Override |
164 | - public String executeToString(TbMsg msg) throws ScriptException { | ||
165 | - JsonNode result = executeScript(msg); | ||
166 | - if (!result.isTextual()) { | ||
167 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
168 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | ||
169 | - } | ||
170 | - return result.asText(); | 151 | + public ListenableFuture<String> executeToStringAsync(TbMsg msg) { |
152 | + return Futures.transformAsync(executeScriptAsync(msg), | ||
153 | + this::executeToStringTransform, | ||
154 | + MoreExecutors.directExecutor()); | ||
171 | } | 155 | } |
172 | 156 | ||
173 | - @Override | ||
174 | - public boolean executeFilter(TbMsg msg) throws ScriptException { | ||
175 | - JsonNode result = executeScript(msg); | ||
176 | - if (!result.isBoolean()) { | ||
177 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
178 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | 157 | + ListenableFuture<String> executeToStringTransform(JsonNode result) { |
158 | + if (result.isTextual()) { | ||
159 | + return Futures.immediateFuture(result.asText()); | ||
179 | } | 160 | } |
180 | - return result.asBoolean(); | 161 | + log.warn("Wrong result type: {}", result.getNodeType()); |
162 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | ||
181 | } | 163 | } |
182 | 164 | ||
183 | @Override | 165 | @Override |
184 | public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { | 166 | public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { |
185 | - ListenableFuture<JsonNode> result = executeScriptAsync(msg); | ||
186 | - return Futures.transformAsync(result, json -> { | ||
187 | - if (!json.isBoolean()) { | ||
188 | - log.warn("Wrong result type: {}", json.getNodeType()); | ||
189 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
190 | - } else { | ||
191 | - return Futures.immediateFuture(json.asBoolean()); | ||
192 | - } | ||
193 | - }, MoreExecutors.directExecutor()); | 167 | + return Futures.transformAsync(executeScriptAsync(msg), |
168 | + this::executeFilterTransform, | ||
169 | + MoreExecutors.directExecutor()); | ||
194 | } | 170 | } |
195 | 171 | ||
196 | - @Override | ||
197 | - public Set<String> executeSwitch(TbMsg msg) throws ScriptException { | ||
198 | - JsonNode result = executeScript(msg); | 172 | + ListenableFuture<Boolean> executeFilterTransform(JsonNode json) { |
173 | + if (json.isBoolean()) { | ||
174 | + return Futures.immediateFuture(json.asBoolean()); | ||
175 | + } | ||
176 | + log.warn("Wrong result type: {}", json.getNodeType()); | ||
177 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
178 | + } | ||
179 | + | ||
180 | + ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) { | ||
199 | if (result.isTextual()) { | 181 | if (result.isTextual()) { |
200 | - return Collections.singleton(result.asText()); | ||
201 | - } else if (result.isArray()) { | ||
202 | - Set<String> nextStates = Sets.newHashSet(); | 182 | + return Futures.immediateFuture(Collections.singleton(result.asText())); |
183 | + } | ||
184 | + if (result.isArray()) { | ||
185 | + Set<String> nextStates = new HashSet<>(); | ||
203 | for (JsonNode val : result) { | 186 | for (JsonNode val : result) { |
204 | if (!val.isTextual()) { | 187 | if (!val.isTextual()) { |
205 | log.warn("Wrong result type: {}", val.getNodeType()); | 188 | log.warn("Wrong result type: {}", val.getNodeType()); |
206 | - throw new ScriptException("Wrong result type: " + val.getNodeType()); | 189 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType())); |
207 | } else { | 190 | } else { |
208 | nextStates.add(val.asText()); | 191 | nextStates.add(val.asText()); |
209 | } | 192 | } |
210 | } | 193 | } |
211 | - return nextStates; | ||
212 | - } else { | ||
213 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
214 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | 194 | + return Futures.immediateFuture(nextStates); |
215 | } | 195 | } |
196 | + log.warn("Wrong result type: {}", result.getNodeType()); | ||
197 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | ||
216 | } | 198 | } |
217 | 199 | ||
218 | - private JsonNode executeScript(TbMsg msg) throws ScriptException { | ||
219 | - try { | ||
220 | - String[] inArgs = prepareArgs(msg); | ||
221 | - String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); | ||
222 | - return mapper.readTree(eval); | ||
223 | - } catch (ExecutionException e) { | ||
224 | - if (e.getCause() instanceof ScriptException) { | ||
225 | - throw (ScriptException) e.getCause(); | ||
226 | - } else if (e.getCause() instanceof RuntimeException) { | ||
227 | - throw new ScriptException(e.getCause().getMessage()); | ||
228 | - } else { | ||
229 | - throw new ScriptException(e); | ||
230 | - } | ||
231 | - } catch (Exception e) { | ||
232 | - throw new ScriptException(e); | ||
233 | - } | 200 | + @Override |
201 | + public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { | ||
202 | + return Futures.transformAsync(executeScriptAsync(msg), | ||
203 | + this::executeSwitchTransform, | ||
204 | + MoreExecutors.directExecutor()); //usually runs in a callbackExecutor | ||
234 | } | 205 | } |
235 | 206 | ||
236 | - private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { | 207 | + ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { |
208 | + log.trace("execute script async, msg {}", msg); | ||
237 | String[] inArgs = prepareArgs(msg); | 209 | String[] inArgs = prepareArgs(msg); |
238 | - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), | 210 | + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); |
211 | + } | ||
212 | + | ||
213 | + ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) { | ||
214 | + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args), | ||
239 | o -> { | 215 | o -> { |
240 | try { | 216 | try { |
241 | return Futures.immediateFuture(mapper.readTree(o.toString())); | 217 | return Futures.immediateFuture(mapper.readTree(o.toString())); |
@@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId; | @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId; | ||
33 | import org.thingsboard.server.common.data.id.DashboardId; | 33 | import org.thingsboard.server.common.data.id.DashboardId; |
34 | import org.thingsboard.server.common.data.id.IdBased; | 34 | import org.thingsboard.server.common.data.id.IdBased; |
35 | import org.thingsboard.server.common.data.id.TenantId; | 35 | import org.thingsboard.server.common.data.id.TenantId; |
36 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | ||
37 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | 36 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
37 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | ||
38 | import org.thingsboard.server.common.data.page.PageData; | 38 | import org.thingsboard.server.common.data.page.PageData; |
39 | import org.thingsboard.server.common.data.page.PageLink; | 39 | import org.thingsboard.server.common.data.page.PageLink; |
40 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | 40 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
@@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper { | @@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper { | ||
93 | 93 | ||
94 | private final Lock userCreationLock = new ReentrantLock(); | 94 | private final Lock userCreationLock = new ReentrantLock(); |
95 | 95 | ||
96 | - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { | 96 | + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { |
97 | 97 | ||
98 | - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | 98 | + OAuth2MapperConfig config = registration.getMapperConfig(); |
99 | 99 | ||
100 | UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); | 100 | UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); |
101 | 101 | ||
@@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper { | @@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper { | ||
139 | } | 139 | } |
140 | } | 140 | } |
141 | 141 | ||
142 | - if (clientRegistration.getAdditionalInfo() != null && | ||
143 | - clientRegistration.getAdditionalInfo().has("providerName")) { | ||
144 | - additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); | 142 | + if (registration.getAdditionalInfo() != null && |
143 | + registration.getAdditionalInfo().has("providerName")) { | ||
144 | + additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText()); | ||
145 | } | 145 | } |
146 | 146 | ||
147 | user.setAdditionalInfo(additionalInfo); | 147 | user.setAdditionalInfo(additionalInfo); |
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 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import lombok.extern.slf4j.Slf4j; | ||
20 | +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | ||
21 | +import org.springframework.stereotype.Service; | ||
22 | +import org.springframework.util.LinkedMultiValueMap; | ||
23 | +import org.springframework.util.MultiValueMap; | ||
24 | +import org.springframework.util.StringUtils; | ||
25 | +import org.thingsboard.common.util.JacksonUtil; | ||
26 | +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | ||
27 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | ||
28 | +import org.thingsboard.server.dao.oauth2.OAuth2User; | ||
29 | +import org.thingsboard.server.service.security.model.SecurityUser; | ||
30 | + | ||
31 | +import javax.servlet.http.HttpServletRequest; | ||
32 | +import java.util.HashMap; | ||
33 | +import java.util.Map; | ||
34 | + | ||
35 | +@Service(value = "appleOAuth2ClientMapper") | ||
36 | +@Slf4j | ||
37 | +public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { | ||
38 | + | ||
39 | + private static final String USER = "user"; | ||
40 | + private static final String NAME = "name"; | ||
41 | + private static final String FIRST_NAME = "firstName"; | ||
42 | + private static final String LAST_NAME = "lastName"; | ||
43 | + private static final String EMAIL = "email"; | ||
44 | + | ||
45 | + @Override | ||
46 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | ||
47 | + OAuth2MapperConfig config = registration.getMapperConfig(); | ||
48 | + Map<String, Object> attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes()); | ||
49 | + String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); | ||
50 | + OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); | ||
51 | + | ||
52 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); | ||
53 | + } | ||
54 | + | ||
55 | + private static Map<String, Object> updateAttributesFromRequestParams(HttpServletRequest request, Map<String, Object> attributes) { | ||
56 | + Map<String, Object> updated = attributes; | ||
57 | + MultiValueMap<String, String> params = toMultiMap(request.getParameterMap()); | ||
58 | + String userValue = params.getFirst(USER); | ||
59 | + if (StringUtils.hasText(userValue)) { | ||
60 | + JsonNode user = null; | ||
61 | + try { | ||
62 | + user = JacksonUtil.toJsonNode(userValue); | ||
63 | + } catch (Exception e) {} | ||
64 | + if (user != null) { | ||
65 | + updated = new HashMap<>(attributes); | ||
66 | + if (user.has(NAME)) { | ||
67 | + JsonNode name = user.get(NAME); | ||
68 | + if (name.isObject()) { | ||
69 | + JsonNode firstName = name.get(FIRST_NAME); | ||
70 | + if (firstName != null && firstName.isTextual()) { | ||
71 | + updated.put(FIRST_NAME, firstName.asText()); | ||
72 | + } | ||
73 | + JsonNode lastName = name.get(LAST_NAME); | ||
74 | + if (lastName != null && lastName.isTextual()) { | ||
75 | + updated.put(LAST_NAME, lastName.asText()); | ||
76 | + } | ||
77 | + } | ||
78 | + } | ||
79 | + if (user.has(EMAIL)) { | ||
80 | + JsonNode email = user.get(EMAIL); | ||
81 | + if (email != null && email.isTextual()) { | ||
82 | + updated.put(EMAIL, email.asText()); | ||
83 | + } | ||
84 | + } | ||
85 | + } | ||
86 | + } | ||
87 | + return updated; | ||
88 | + } | ||
89 | + | ||
90 | + private static MultiValueMap<String, String> toMultiMap(Map<String, String[]> map) { | ||
91 | + MultiValueMap<String, String> params = new LinkedMultiValueMap<>(map.size()); | ||
92 | + map.forEach((key, values) -> { | ||
93 | + if (values.length > 0) { | ||
94 | + for (String value : values) { | ||
95 | + params.add(key, value); | ||
96 | + } | ||
97 | + } | ||
98 | + }); | ||
99 | + return params; | ||
100 | + } | ||
101 | +} |
@@ -18,11 +18,12 @@ package org.thingsboard.server.service.security.auth.oauth2; | @@ -18,11 +18,12 @@ package org.thingsboard.server.service.security.auth.oauth2; | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | 19 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
20 | import org.springframework.stereotype.Service; | 20 | import org.springframework.stereotype.Service; |
21 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | ||
22 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | 21 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
22 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | ||
23 | import org.thingsboard.server.dao.oauth2.OAuth2User; | 23 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
24 | import org.thingsboard.server.service.security.model.SecurityUser; | 24 | import org.thingsboard.server.service.security.model.SecurityUser; |
25 | 25 | ||
26 | +import javax.servlet.http.HttpServletRequest; | ||
26 | import java.util.Map; | 27 | import java.util.Map; |
27 | 28 | ||
28 | @Service(value = "basicOAuth2ClientMapper") | 29 | @Service(value = "basicOAuth2ClientMapper") |
@@ -30,12 +31,12 @@ import java.util.Map; | @@ -30,12 +31,12 @@ import java.util.Map; | ||
30 | public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { | 31 | public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
31 | 32 | ||
32 | @Override | 33 | @Override |
33 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | ||
34 | - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | 34 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { |
35 | + OAuth2MapperConfig config = registration.getMapperConfig(); | ||
35 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); | 36 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
36 | String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); | 37 | String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); |
37 | OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); | 38 | OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); |
38 | 39 | ||
39 | - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); | 40 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); |
40 | } | 41 | } |
41 | } | 42 | } |
@@ -23,12 +23,14 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic | @@ -23,12 +23,14 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic | ||
23 | import org.springframework.stereotype.Service; | 23 | import org.springframework.stereotype.Service; |
24 | import org.springframework.util.StringUtils; | 24 | import org.springframework.util.StringUtils; |
25 | import org.springframework.web.client.RestTemplate; | 25 | import org.springframework.web.client.RestTemplate; |
26 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | ||
27 | import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; | 26 | import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; |
28 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | 27 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
28 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | ||
29 | import org.thingsboard.server.dao.oauth2.OAuth2User; | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
30 | import org.thingsboard.server.service.security.model.SecurityUser; | 30 | import org.thingsboard.server.service.security.model.SecurityUser; |
31 | 31 | ||
32 | +import javax.servlet.http.HttpServletRequest; | ||
33 | + | ||
32 | @Service(value = "customOAuth2ClientMapper") | 34 | @Service(value = "customOAuth2ClientMapper") |
33 | @Slf4j | 35 | @Slf4j |
34 | public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { | 36 | public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
@@ -39,10 +41,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme | @@ -39,10 +41,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme | ||
39 | private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); | 41 | private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); |
40 | 42 | ||
41 | @Override | 43 | @Override |
42 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | ||
43 | - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | 44 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { |
45 | + OAuth2MapperConfig config = registration.getMapperConfig(); | ||
44 | OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); | 46 | OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); |
45 | - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); | 47 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); |
46 | } | 48 | } |
47 | 49 | ||
48 | private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { | 50 | private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { |
@@ -23,12 +23,13 @@ import org.springframework.boot.web.client.RestTemplateBuilder; | @@ -23,12 +23,13 @@ import org.springframework.boot.web.client.RestTemplateBuilder; | ||
23 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | 23 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
24 | import org.springframework.stereotype.Service; | 24 | import org.springframework.stereotype.Service; |
25 | import org.springframework.web.client.RestTemplate; | 25 | import org.springframework.web.client.RestTemplate; |
26 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | ||
27 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | 26 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
27 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | ||
28 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | 28 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
29 | import org.thingsboard.server.dao.oauth2.OAuth2User; | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
30 | import org.thingsboard.server.service.security.model.SecurityUser; | 30 | import org.thingsboard.server.service.security.model.SecurityUser; |
31 | 31 | ||
32 | +import javax.servlet.http.HttpServletRequest; | ||
32 | import java.util.ArrayList; | 33 | import java.util.ArrayList; |
33 | import java.util.Map; | 34 | import java.util.Map; |
34 | import java.util.Optional; | 35 | import java.util.Optional; |
@@ -46,13 +47,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme | @@ -46,13 +47,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme | ||
46 | private OAuth2Configuration oAuth2Configuration; | 47 | private OAuth2Configuration oAuth2Configuration; |
47 | 48 | ||
48 | @Override | 49 | @Override |
49 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | ||
50 | - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | 50 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { |
51 | + OAuth2MapperConfig config = registration.getMapperConfig(); | ||
51 | Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); | 52 | Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); |
52 | String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); | 53 | String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); |
53 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); | 54 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
54 | OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); | 55 | OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); |
55 | - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); | 56 | + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration); |
56 | } | 57 | } |
57 | 58 | ||
58 | private synchronized String getEmail(String emailUrl, String oauth2Token) { | 59 | private synchronized String getEmail(String emailUrl, String oauth2Token) { |
@@ -16,9 +16,12 @@ | @@ -16,9 +16,12 @@ | ||
16 | package org.thingsboard.server.service.security.auth.oauth2; | 16 | package org.thingsboard.server.service.security.auth.oauth2; |
17 | 17 | ||
18 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | 18 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
19 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | 19 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; |
20 | +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; | ||
20 | import org.thingsboard.server.service.security.model.SecurityUser; | 21 | import org.thingsboard.server.service.security.model.SecurityUser; |
21 | 22 | ||
23 | +import javax.servlet.http.HttpServletRequest; | ||
24 | + | ||
22 | public interface OAuth2ClientMapper { | 25 | public interface OAuth2ClientMapper { |
23 | - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); | 26 | + SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); |
24 | } | 27 | } |
@@ -37,6 +37,10 @@ public class OAuth2ClientMapperProvider { | @@ -37,6 +37,10 @@ public class OAuth2ClientMapperProvider { | ||
37 | @Qualifier("githubOAuth2ClientMapper") | 37 | @Qualifier("githubOAuth2ClientMapper") |
38 | private OAuth2ClientMapper githubOAuth2ClientMapper; | 38 | private OAuth2ClientMapper githubOAuth2ClientMapper; |
39 | 39 | ||
40 | + @Autowired | ||
41 | + @Qualifier("appleOAuth2ClientMapper") | ||
42 | + private OAuth2ClientMapper appleOAuth2ClientMapper; | ||
43 | + | ||
40 | public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { | 44 | public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { |
41 | switch (oauth2MapperType) { | 45 | switch (oauth2MapperType) { |
42 | case CUSTOM: | 46 | case CUSTOM: |
@@ -45,6 +49,8 @@ public class OAuth2ClientMapperProvider { | @@ -45,6 +49,8 @@ public class OAuth2ClientMapperProvider { | ||
45 | return basicOAuth2ClientMapper; | 49 | return basicOAuth2ClientMapper; |
46 | case GITHUB: | 50 | case GITHUB: |
47 | return githubOAuth2ClientMapper; | 51 | return githubOAuth2ClientMapper; |
52 | + case APPLE: | ||
53 | + return appleOAuth2ClientMapper; | ||
48 | default: | 54 | default: |
49 | throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); | 55 | throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); |
50 | } | 56 | } |
@@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2; | @@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2; | ||
18 | import org.springframework.beans.factory.annotation.Autowired; | 18 | import org.springframework.beans.factory.annotation.Autowired; |
19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
20 | import org.springframework.security.core.AuthenticationException; | 20 | import org.springframework.security.core.AuthenticationException; |
21 | +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; | ||
21 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; | 22 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; |
22 | import org.springframework.stereotype.Component; | 23 | import org.springframework.stereotype.Component; |
24 | +import org.thingsboard.server.common.data.StringUtils; | ||
23 | import org.thingsboard.server.common.data.id.CustomerId; | 25 | import org.thingsboard.server.common.data.id.CustomerId; |
24 | import org.thingsboard.server.common.data.id.EntityId; | 26 | import org.thingsboard.server.common.data.id.EntityId; |
25 | import org.thingsboard.server.common.data.id.TenantId; | 27 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -34,7 +36,6 @@ import java.net.URLEncoder; | @@ -34,7 +36,6 @@ import java.net.URLEncoder; | ||
34 | import java.nio.charset.StandardCharsets; | 36 | import java.nio.charset.StandardCharsets; |
35 | 37 | ||
36 | @Component(value = "oauth2AuthenticationFailureHandler") | 38 | @Component(value = "oauth2AuthenticationFailureHandler") |
37 | -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") | ||
38 | public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { | 39 | public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { |
39 | 40 | ||
40 | private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; | 41 | private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; |
@@ -51,9 +52,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF | @@ -51,9 +52,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF | ||
51 | public void onAuthenticationFailure(HttpServletRequest request, | 52 | public void onAuthenticationFailure(HttpServletRequest request, |
52 | HttpServletResponse response, AuthenticationException exception) | 53 | HttpServletResponse response, AuthenticationException exception) |
53 | throws IOException, ServletException { | 54 | throws IOException, ServletException { |
54 | - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); | 55 | + String baseUrl; |
56 | + String errorPrefix; | ||
57 | + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request); | ||
58 | + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME); | ||
59 | + if (!StringUtils.isEmpty(callbackUrlScheme)) { | ||
60 | + baseUrl = callbackUrlScheme + ":"; | ||
61 | + errorPrefix = "/?error="; | ||
62 | + } else { | ||
63 | + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); | ||
64 | + errorPrefix = "/login?loginError="; | ||
65 | + } | ||
55 | httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); | 66 | httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); |
56 | - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + | 67 | + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix + |
57 | URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); | 68 | URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); |
58 | } | 69 | } |
59 | } | 70 | } |