From bf198a34aaf822aa902b0bece5b9bb5e16bdea6d Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 25 Sep 2019 19:52:12 +0300 Subject: [PATCH 001/120] Added 'Device claiming widget' --- .../system/widget_bundles/input_widgets.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 5129ea19a7..59830b4748 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -292,6 +292,22 @@ "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } + }, + { + "alias": "device_claiming_widget", + "name": "Device claiming widget", + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 4, + "resources": [], + "templateHtml": "
\n
\n \n \n \n
\n
Device name is required.
\n
\n
\n \n \n \n
\n
Device secret is required.
\n
\n
\n
\n Claim\n
", + "templateCss": ".claim-form {\n margin: 16px;\n}", + "controllerScript": "let $scope;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n let $injector = $scope.$injector;\n let $q = $injector.get('$q');\n let $http = $injector.get('$http');\n let toast = $scope.$injector.get('toast');\n $scope.deviceSecretField = self.ctx.settings.deviceSecret;\n $scope.deviceObj = {};\n \n $scope.claim = () => {\n $scope.loading = true;\n claimDevice($scope.deviceObj.deviceName, $scope.deviceObj.deviceSecret).then(\n (data) => {\n resetForm();\n $scope.claimDeviceForm.$setPristine();\n $scope.claimDeviceForm.$setUntouched();\n $scope.loading = false;\n if (data.response == \"SUCCESS\") {\n toast.showSuccess('Device was successfully claimed!', 2000, angular.element('.claim-form'), 'bottom left');\n }\n },\n () => {\n $scope.loading = false;\n }\n );\n }\n \n function claimDevice(deviceName, deviceSecret, config) {\n let deferred = $q.defer();\n let url = \"/api/customer/device/\" + deviceName + \"/claim\";\n let obj = deviceSecret ? { secretKey: deviceSecret } : {};\n $http.post(url, obj, config).then(\n (payload) => {\n deferred.resolve(payload.data); \n },\n () => {\n deferred.reject();\n }\n );\n return deferred.promise;\n }\n \n function resetForm() {\n $scope.deviceObj = {};\n }\n}\n\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [],\n \"properties\": {\n \"deviceSecret\": {\n \"title\": \"Add 'Device secret' field\",\n \"type\": \"boolean\",\n \"default\": false\n }\n }\n },\n \"form\": [\n \"deviceSecret\"\n ]\n}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"deviceSecret\":true},\"title\":\"Device claiming widget\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"enableDataExport\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } } ] -} +} \ No newline at end of file From e38ec2e3fec211fc4027b213952aba2b308441a7 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 27 Sep 2019 11:39:20 +0300 Subject: [PATCH 002/120] Missed translations for login page --- ui/src/app/locale/locale.constant-ru_RU.json | 1 + ui/src/app/locale/locale.constant-uk_UA.json | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index d1c29f2a93..c7d090eefa 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1137,6 +1137,7 @@ "remember-me": "Запомнить меня", "forgot-password": "Забыли пароль?", "password-reset": "Пароль сброшен", + "expired-password-reset-message": "Срок действия Вашего пароля закончился! Пожалуйста, создайте новый пароль.", "new-password": "Новый пароль", "new-password-again": "Повторите новый пароль", "password-link-sent-message": "Ссылка для сброса пароля была успешно отправлена!", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 3cc2b8f1b3..ad8b5682fa 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -1552,6 +1552,7 @@ "remember-me": "Запам'ятати мене", "forgot-password": "Забули пароль?", "password-reset": "Скидання пароля", + "expired-password-reset-message": "Термін дії Вашого паролю закінчився! Будь ласка, створіть новий пароль.", "new-password": "Новий пароль", "new-password-again": "Повторіть новий пароль", "password-link-sent-message": "Посилання для скидання пароля було успішно надіслано!", From bd8f9ba9882bcfe51786dbd5d75bda00c37d7d0c Mon Sep 17 00:00:00 2001 From: luPhz <48518765+luPhz@users.noreply.github.com> Date: Tue, 1 Oct 2019 15:14:06 +0200 Subject: [PATCH 003/120] Fixed spelling --- ui/src/app/locale/locale.constant-de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index caf350cc13..703e975a44 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -200,7 +200,7 @@ "root-entity": "Wurzelentität", "state-entity-parameter-name": "Parameter-Name der Statusentität", "default-state-entity": "Standard Statusentität", - "default-entity-parameter-name": "Standartmäßig", + "default-entity-parameter-name": "Standardmäßig", "max-relation-level": "Maximale Beziehungstiefe", "unlimited-level": "Unbegrenzte Ebenen", "state-entity": "Dashboard Status Entität", From ea39088fe8ae1e935c33a3032dcaa01f672ce097 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 9 Oct 2019 19:25:20 +0300 Subject: [PATCH 004/120] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6f22da9139..94ce1b76e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ before_install: - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false jdk: - - oraclejdk8 + - openjdk8 language: java sudo: required services: From fb369d1d00de65698b6acd42b9a80be4b866d658 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 8 Oct 2019 17:38:32 +0300 Subject: [PATCH 005/120] Change CDN url(end life RawGit) --- ui/src/app/widget/lib/google-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index 649290ef1e..d9e1793f12 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -87,7 +87,7 @@ export default class TbGoogleMap { this.initMapFunctionName = 'initGoogleMap_' + this.mapId; window[this.initMapFunctionName] = function() { // eslint-disable-line no-undef, angular/window-service - lazyLoad.load({ type: 'js', path: 'https://cdn.rawgit.com/googlemaps/v3-utility-library/master/markerwithlabel/src/markerwithlabel.js' }).then( // eslint-disable-line no-undef + lazyLoad.load({ type: 'js', path: 'https://unpkg.com/@google/@1.2.3/src/markerwithlabel.js' }).then( // eslint-disable-line no-undef function success() { gmGlobals.gmApiKeys[tbMap.apiKey].loaded = true; initGoogleMap(); From 53ea572be6c051c5d987ec3b33179239c4f5ea91 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 9 Oct 2019 19:00:49 +0300 Subject: [PATCH 006/120] Create new input widget for location with automaticali detection --- .../system/widget_bundles/input_widgets.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 59830b4748..c6df5a1fdb 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -101,6 +101,22 @@ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server image attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, + { + "alias": "update_server_location_attribute", + "name": "Update server location attribute", + "descriptor": { + "type": "latest", + "sizeX": 7.5, + "sizeY": 3, + "resources": [], + "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n my_location\n {{ 'widgets.input-widgets.get-location' | translate }}\n \n \n \n check\n {{ 'widgets.input-widgets.update-timeseries' | translate }}\n \n \n close\n {{ 'widgets.input-widgets.discard-changes' | translate }}\n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .md-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form md-input-container{\n width: 100%;\n margin: 18px 0 5px;\n}\n\n.attribute-update-form.small-width md-input-container{\n width: 150px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\n\r\nself.onInit = function() {\r\n console.log(self.ctx);\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row') ? true : false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || $translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || $translate.instant('widgets.input-widgets.longitude');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != types.dataKeyType.attribute){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function() {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entityType,\r\n datasource.entityId,\r\n types.attributesScope.server.value,\r\n [{\r\n key: settings.latKeyName,\r\n value: $scope.currentLat\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.currentLng\r\n }]\r\n ).then(\r\n function success() {\r\n $scope.originalLat = $scope.currentLat;\r\n $scope.originalLng = $scope.currentLng;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000,\r\n angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function() {\r\n if ($scope.currentLat === $scope.originalLat || $scope.currentLng ===$scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n $scope.discardChange = function() {\r\n $scope.currentLat = $scope.originalLat;\r\n $scope.currentLng = $scope.originalLng;\r\n scope.isFocused = false;\r\n }\r\n \r\n $scope.disableButton = function () {\r\n return $scope.currentLat === $scope.originalLat && $scope.currentLng === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null ;\r\n }\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n toast.showError($translate.instant('widgets.input-widgets.blocked-location'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n toast.showError($translate.instant('widgets.input-widgets.no-support-geolocation'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n \r\n function showPosition(position) {\r\n $scope.currentLat = position.coords.latitude;\r\n $scope.currentLng = position.coords.longitude;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName){\r\n $scope.currentLat = $scope.originalLat = self.ctx.data[i].data[0][1];\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName){\r\n $scope.currentLng = $scope.originalLng = self.ctx.data[i].data[0][1];\r\n }\r\n }\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container[0].offsetWidth < 320) ? true : false;\r\n $scope.changeAlignment = ($scope.isHorizontal && (self.ctx.$container[0].offsetWidth < 480)) ? true : false;\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"latKeyName\": {\n \"title\": \"Latitude key name\",\n \"type\": \"string\",\n \"default\": \"latitude\"\n },\n \"lngKeyName\": {\n \"title\": \"Longitude key name\",\n \"type\": \"string\",\n \"default\": \"longitude\"\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"latLabel\": {\n \"title\": \"Label for latitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"lngLabel\": {\n \"title\": \"Label for longitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\": {\n \"title\": \"Show result message\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableHighAccuracy\": {\n \"title\": \"Use high accuracy\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showGetLocation\": {\n \"title\": \"Show button 'Get current location'\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"inputFieldsAlignment\": {\n \"title\": \"Input fields alignment\",\n \"type\": \"string\",\n \"default\": \"column\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"latKeyName\",\n \"lngKeyName\",\n \"enableHighAccuracy\",\n \"showGetLocation\",\n \"showResultMessage\",\n {\n \"key\": \"inputFieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"column\",\n \"label\": \"Column (default)\"\n },\n {\n \"value\": \"row\",\n \"label\": \"Row\"\n }\n ]\n },\n \"showLabel\",\n \"latLabel\",\n \"lngLabel\",\n \"requiredErrorMessage\"\n ]\n}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server location attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } + }, { "alias": "update_shared_string_attribute", "name": "Update shared string attribute", @@ -197,6 +213,22 @@ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared image attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, + { + "alias": "update_shared_location_attribute", + "name": "Update shared location attribute", + "descriptor": { + "type": "latest", + "sizeX": 7.5, + "sizeY": 3, + "resources": [], + "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n my_location\n {{ 'widgets.input-widgets.get-location' | translate }}\n \n \n \n check\n {{ 'widgets.input-widgets.update-timeseries' | translate }}\n \n \n close\n {{ 'widgets.input-widgets.discard-changes' | translate }}\n \n
\n
\n \n
\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .md-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form md-input-container{\n width: 100%;\n margin: 18px 0 5px;\n}\n\n.attribute-update-form.small-width md-input-container{\n width: 150px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\n\r\nself.onInit = function() {\r\n console.log(self.ctx);\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row') ? true : false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || $translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || $translate.instant('widgets.input-widgets.longitude');\r\n $scope.message = $translate.instant('widgets.input-widgets.no-entity-selected');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType === types.entityType.device) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n \r\n $scope.entityDetected = true;\r\n }\r\n } else {\r\n $scope.message = $translate.instant('widgets.input-widgets.not-allowed-entity');\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != types.dataKeyType.attribute){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function() {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entityType,\r\n datasource.entityId,\r\n types.attributesScope.server.value,\r\n [{\r\n key: settings.latKeyName,\r\n value: $scope.currentLat\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.currentLng\r\n }]\r\n ).then(\r\n function success() {\r\n $scope.originalLat = $scope.currentLat;\r\n $scope.originalLng = $scope.currentLng;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000,\r\n angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function() {\r\n if ($scope.currentLat === $scope.originalLat || $scope.currentLng ===$scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n $scope.discardChange = function() {\r\n $scope.currentLat = $scope.originalLat;\r\n $scope.currentLng = $scope.originalLng;\r\n scope.isFocused = false;\r\n }\r\n \r\n $scope.disableButton = function () {\r\n return $scope.currentLat === $scope.originalLat && $scope.currentLng === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null ;\r\n }\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n toast.showError($translate.instant('widgets.input-widgets.blocked-location'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n toast.showError($translate.instant('widgets.input-widgets.no-support-geolocation'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n \r\n function showPosition(position) {\r\n $scope.currentLat = position.coords.latitude;\r\n $scope.currentLng = position.coords.longitude;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName){\r\n $scope.currentLat = $scope.originalLat = self.ctx.data[i].data[0][1];\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName){\r\n $scope.currentLng = $scope.originalLng = self.ctx.data[i].data[0][1];\r\n }\r\n }\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container[0].offsetWidth < 320) ? true : false;\r\n $scope.changeAlignment = ($scope.isHorizontal && (self.ctx.$container[0].offsetWidth < 480)) ? true : false;\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"latKeyName\": {\n \"title\": \"Latitude key name\",\n \"type\": \"string\",\n \"default\": \"latitude\"\n },\n \"lngKeyName\": {\n \"title\": \"Longitude key name\",\n \"type\": \"string\",\n \"default\": \"longitude\"\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"latLabel\": {\n \"title\": \"Label for latitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"lngLabel\": {\n \"title\": \"Label for longitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\": {\n \"title\": \"Show result message\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableHighAccuracy\": {\n \"title\": \"Use high accuracy\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showGetLocation\": {\n \"title\": \"Show button 'Get current location'\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"inputFieldsAlignment\": {\n \"title\": \"Input fields alignment\",\n \"type\": \"string\",\n \"default\": \"column\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"latKeyName\",\n \"lngKeyName\",\n \"enableHighAccuracy\",\n \"showGetLocation\",\n \"showResultMessage\",\n {\n \"key\": \"inputFieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"column\",\n \"label\": \"Column (default)\"\n },\n {\n \"value\": \"row\",\n \"label\": \"Row\"\n }\n ]\n },\n \"showLabel\",\n \"latLabel\",\n \"lngLabel\",\n \"requiredErrorMessage\"\n ]\n}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared location attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } + }, { "alias": "update_string_timeseries", "name": "Update string timeseries", @@ -261,6 +293,22 @@ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, + { + "alias": "update_location_timeseries", + "name": "Update location timeseries", + "descriptor": { + "type": "latest", + "sizeX": 7.5, + "sizeY": 3, + "resources": [], + "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n my_location\n {{ 'widgets.input-widgets.get-location' | translate }}\n \n \n \n check\n {{ 'widgets.input-widgets.update-timeseries' | translate }}\n \n \n close\n {{ 'widgets.input-widgets.discard-changes' | translate }}\n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .md-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form md-input-container{\n width: 100%;\n margin: 18px 0 5px;\n}\n\n.attribute-update-form.small-width md-input-container{\n width: 150px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\n\r\nself.onInit = function() {\r\n console.log(self.ctx);\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row') ? true : false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || $translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || $translate.instant('widgets.input-widgets.longitude');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != types.dataKeyType.timeseries){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function() {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityTimeseries(\r\n datasource.entityType,\r\n datasource.entityId,\r\n 'scope',\r\n [{\r\n key: settings.latKeyName,\r\n value: $scope.currentLat\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.currentLng\r\n }]\r\n ).then(\r\n function success() {\r\n $scope.originalLat = $scope.currentLat;\r\n $scope.originalLng = $scope.currentLng;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000,\r\n angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function() {\r\n if ($scope.currentLat === $scope.originalLat || $scope.currentLng ===$scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n $scope.discardChange = function() {\r\n $scope.currentLat = $scope.originalLat;\r\n $scope.currentLng = $scope.originalLng;\r\n scope.isFocused = false;\r\n }\r\n \r\n $scope.disableButton = function () {\r\n return $scope.currentLat === $scope.originalLat && $scope.currentLng === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null ;\r\n }\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n toast.showError($translate.instant('widgets.input-widgets.blocked-location'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n toast.showError($translate.instant('widgets.input-widgets.no-support-geolocation'), \r\n angular.element(self.ctx.$container),'bottom left');\r\n }\r\n }\r\n \r\n function showPosition(position) {\r\n $scope.currentLat = position.coords.latitude;\r\n $scope.currentLng = position.coords.longitude;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName){\r\n $scope.currentLat = $scope.originalLat = self.ctx.data[i].data[0][1];\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName){\r\n $scope.currentLng = $scope.originalLng = self.ctx.data[i].data[0][1];\r\n }\r\n }\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container[0].offsetWidth < 320) ? true : false;\r\n $scope.changeAlignment = ($scope.isHorizontal && (self.ctx.$container[0].offsetWidth < 480)) ? true : false;\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"latKeyName\": {\n \"title\": \"Latitude key name\",\n \"type\": \"string\",\n \"default\": \"latitude\"\n },\n \"lngKeyName\": {\n \"title\": \"Longitude key name\",\n \"type\": \"string\",\n \"default\": \"longitude\"\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"latLabel\": {\n \"title\": \"Label for latitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"lngLabel\": {\n \"title\": \"Label for longitude\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\": {\n \"title\": \"Show result message\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableHighAccuracy\": {\n \"title\": \"Use high accuracy\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showGetLocation\": {\n \"title\": \"Show button 'Get current location'\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"inputFieldsAlignment\": {\n \"title\": \"Input fields alignment\",\n \"type\": \"string\",\n \"default\": \"column\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"latKeyName\",\n \"lngKeyName\",\n \"enableHighAccuracy\",\n \"showGetLocation\",\n \"showResultMessage\",\n {\n \"key\": \"inputFieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"column\",\n \"label\": \"Column (default)\"\n },\n {\n \"value\": \"row\",\n \"label\": \"Row\"\n }\n ]\n },\n \"showLabel\",\n \"latLabel\",\n \"lngLabel\",\n \"requiredErrorMessage\"\n ]\n}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update location timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } + }, { "alias": "update_multiple_attributes", "name": "Update Multiple Attributes", From 4e37fb27b3caf8ecb1b8ab6a5a347b3d5b50ce95 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 9 Oct 2019 20:30:11 +0300 Subject: [PATCH 007/120] Fix URL --- ui/src/app/widget/lib/google-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index d9e1793f12..d882056050 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -87,7 +87,7 @@ export default class TbGoogleMap { this.initMapFunctionName = 'initGoogleMap_' + this.mapId; window[this.initMapFunctionName] = function() { // eslint-disable-line no-undef, angular/window-service - lazyLoad.load({ type: 'js', path: 'https://unpkg.com/@google/@1.2.3/src/markerwithlabel.js' }).then( // eslint-disable-line no-undef + lazyLoad.load({ type: 'js', path: 'https://unpkg.com/@google/markerwithlabel@1.2.3/src/markerwithlabel.js' }).then( // eslint-disable-line no-undef function success() { gmGlobals.gmApiKeys[tbMap.apiKey].loaded = true; initGoogleMap(); From 4d215f3191e25fc0fb548f38eae154229de095ec Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 10 Oct 2019 11:16:49 +0300 Subject: [PATCH 008/120] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94ce1b76e0..f635da5a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ language: java sudo: required services: - docker -script: mvn clean verify -Ddockerfile.skip=false -DblackBoxTests.skip=false -DblackBoxTests.skipTailChildContainers=true +script: mvn clean verify -Ddockerfile.skip=false From 4c96e6283c80a1ecf706b185cbe2f1f6dd74ab01 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 9 Oct 2019 19:56:05 +0300 Subject: [PATCH 009/120] Add translate --- ui/src/app/locale/locale.constant-en_US.json | 7 +++++++ ui/src/app/locale/locale.constant-ru_RU.json | 9 ++++++++- ui/src/app/locale/locale.constant-uk_UA.json | 7 +++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 006db27e14..fbeb0a8818 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1699,15 +1699,22 @@ }, "input-widgets": { "attribute-not-allowed": "Attribute parameter cannot be used in this widget", + "blocked-location": "Geolocation is blocked in your browser", "date": "Date", "discard-changes": "Discard changes", "entity-attribute-required": "Entity attribute is required", + "entity-coordinate-required": "Both fields, latitude and longitude, are required", "entity-timeseries-required": "Entity timeseries is required", + "get-location": "Get current location", + "latitude": "Latitude", + "longitude": "Longitude", "not-allowed-entity": "Selected entity cannot have shared attributes", "no-attribute-selected": "No attribute is selected", "no-datakey-selected": "No datakey is selected", + "no-coordinate-specified": "Datakey for latitude/longitude doesn't specified", "no-entity-selected": "No entity selected", "no-image": "No image", + "no-support-geolocation": "Your browser doesn't support geolocation", "no-support-web-camera": "No supported web camera", "no-timeseries-selected": "No timeseries selected", "switch-attribute-value": "Switch entity attribute value", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index c7d090eefa..276c0fe937 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1615,12 +1615,19 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете", + "blocked-location": "Геолокация заблокирована в вашем браузере", "discard-changes": "Отменить изменения", "entity-attribute-required": "Значение атрибута обязателено", + "entity-coordinate-required": "Необходимо указать широту и долготу", "entity-timeseries-required": "Значение телеметрии обязательно", + "get-location": "Получить текущее местоположение", + "latitude": "Широта", + "longitude": "Долгота", "not-allowed-entity": "Выбраный объект не имеет общих атрибутов", "no-attribute-selected": "Атрибут не выбран", "no-entity-selected": "Объект не выбран", + "no-coordinate-specified": "Ключ для широты/долготы не указан", + "no-support-geolocation": "Ваш браузер не поддерживает геолокацию", "no-timeseries-selected": "Параметр телеметрии не выбран", "switch-attribute-value": "Изменить значение атрибута", "switch-timeseries-value": "Изменить значение телеметрии", @@ -1665,4 +1672,4 @@ "cs_CZ": "Чешский" } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index ad8b5682fa..b7310ec27c 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -2182,12 +2182,19 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не може бути вибраний в цьому віджеті", + "blocked-location": "Геолокація заблокована у вашому браузері", "discard-changes": "Скасувати зміни", "entity-attribute-required": "Значення атрибута обов'язкове", + "entity-coordinate-required": "Необхідно вказати широту та довготу", "entity-timeseries-required": "Значення телеметрії обов'язкове", + "get-location": "Отримати поточне місцезнаходження", + "latitude": "Широта", + "longitude": "Довгота", "not-allowed-entity": "Обрана сутність не має спільних атрибутів", "no-attribute-selected": "Атрибут не вибрано", "no-entity-selected": "Сутність не вибрано", + "no-coordinate-specified": "Ключ для широти/довготи не вказаний", + "no-support-geolocation": "Ваш браузер не підтримує геолокацію", "no-timeseries-selected": "Параметр телеметрії не вибрано", "switch-attribute-value": "Змінити значення атрибута", "switch-timeseries-value": "Змінити значення телеметрії", From db67999b71a0503e763fc9af662c68090a01f02d Mon Sep 17 00:00:00 2001 From: Valerii Sosliuk Date: Thu, 10 Oct 2019 13:49:33 +0300 Subject: [PATCH 010/120] Clear Alarm Node fix clearTs and endTs in log message --- .../rule/engine/action/TbClearAlarmNode.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java index f295187170..e94f236a74 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java @@ -69,11 +69,16 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode { ListenableFuture clearFuture = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), details, System.currentTimeMillis()); return Futures.transformAsync(clearFuture, cleared -> { - if (cleared && details != null) { - alarm.setDetails(details); - } - alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); - return Futures.immediateFuture(new AlarmResult(false, false, true, alarm)); + ListenableFuture savedAlarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); + return Futures.transformAsync(savedAlarmFuture, savedAlarm -> { + if (cleared && savedAlarm != null) { + alarm.setDetails(savedAlarm.getDetails()); + alarm.setEndTs(savedAlarm.getEndTs()); + alarm.setClearTs(savedAlarm.getClearTs()); + } + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); + return Futures.immediateFuture(new AlarmResult(false, false, true, alarm)); + }); }); }, ctx.getDbCallbackExecutor()); } From b95dc476cbc59102a336639d84fe879ff6600519 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Mon, 14 Oct 2019 18:09:18 +0300 Subject: [PATCH 011/120] tool for migrating from Postgres to hybrid mode --- tools/pom.xml | 32 +++ .../client/tools/migrator/MigratorTool.java | 93 ++++++++ .../tools/migrator/PgCaLatestMigrator.java | 179 ++++++++++++++ .../PostgresToCassandraTelemetryMigrator.java | 222 ++++++++++++++++++ .../client/tools/migrator/README.md | 62 +++++ .../client/tools/migrator/WriterBuilder.java | 84 +++++++ 6 files changed, 672 insertions(+) create mode 100644 tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java create mode 100644 tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java create mode 100644 tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java create mode 100644 tools/src/main/java/org/thingsboard/client/tools/migrator/README.md create mode 100644 tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java diff --git a/tools/pom.xml b/tools/pom.xml index 7402fe4708..16d511e6db 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -51,6 +51,38 @@ com.google.guava guava + + org.apache.cassandra + cassandra-all + 3.11.4 + compile + + + commons-io + commons-io + 2.6 + compile + + + + + + maven-assembly-plugin + + + + org.thingsboard.client.tools.migrator.MigratorTool + + + + jar-with-dependencies + + + + + + + diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java new file mode 100644 index 0000000000..fdc1917ca8 --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/MigratorTool.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +import java.io.File; + +public class MigratorTool { + + public static void main(String[] args) { + CommandLine cmd = parseArgs(args); + + + try { + File latestSource = new File(cmd.getOptionValue("latestTelemetryFrom")); + File latestSaveDir = new File(cmd.getOptionValue("latestTelemetryOut")); + File tsSource = new File(cmd.getOptionValue("telemetryFrom")); + File tsSaveDir = new File(cmd.getOptionValue("telemetryOut")); + File partitionsSaveDir = new File(cmd.getOptionValue("partitionsOut")); + boolean castEnable = Boolean.parseBoolean(cmd.getOptionValue("castEnable")); + + PgCaLatestMigrator.migrateLatest(latestSource, latestSaveDir, castEnable); + PostgresToCassandraTelemetryMigrator.migrateTs(tsSource, tsSaveDir, partitionsSaveDir, castEnable); + + } catch (Throwable th) { + th.printStackTrace(); + throw new IllegalStateException("failed", th); + } + + } + + private static CommandLine parseArgs(String[] args) { + Options options = new Options(); + + Option latestTsOpt = new Option("latestFrom", "latestTelemetryFrom", true, "latest telemetry source file path"); + latestTsOpt.setRequired(true); + options.addOption(latestTsOpt); + + Option latestTsOutOpt = new Option("latestOut", "latestTelemetryOut", true, "latest telemetry save dir"); + latestTsOutOpt.setRequired(true); + options.addOption(latestTsOutOpt); + + Option tsOpt = new Option("tsFrom", "telemetryFrom", true, "telemetry source file path"); + tsOpt.setRequired(true); + options.addOption(tsOpt); + + Option tsOutOpt = new Option("tsOut", "telemetryOut", true, "sstable save dir"); + tsOutOpt.setRequired(true); + options.addOption(tsOutOpt); + + Option partitionOutOpt = new Option("partitionsOut", "partitionsOut", true, "partitions save dir"); + partitionOutOpt.setRequired(true); + options.addOption(partitionOutOpt); + + Option castOpt = new Option("castEnable", "castEnable", true, "cast String to Double if possible"); + castOpt.setRequired(true); + options.addOption(castOpt); + + HelpFormatter formatter = new HelpFormatter(); + CommandLineParser parser = new BasicParser(); + + try { + return parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("utility-name", options); + + System.exit(1); + } + return null; + } + +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java new file mode 100644 index 0000000000..667f5e6d0f --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java @@ -0,0 +1,179 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import com.google.common.collect.Lists; +import org.apache.cassandra.io.sstable.CQLSSTableWriter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class PgCaLatestMigrator { + + private static final long LOG_BATCH = 1000000; + private static final long rowPerFile = 1000000; + + + private static long linesProcessed = 0; + private static long linesMigrated = 0; + private static long castErrors = 0; + private static long castedOk = 0; + + private static long currentWriterCount = 1; + private static CQLSSTableWriter currentTsWriter = null; + + public static void migrateLatest(File sourceFile, File outDir, boolean castStringsIfPossible) throws IOException { + long startTs = System.currentTimeMillis(); + long stepLineTs = System.currentTimeMillis(); + long stepOkLineTs = System.currentTimeMillis(); + LineIterator iterator = FileUtils.lineIterator(sourceFile); + currentTsWriter = WriterBuilder.getTsWriter(outDir); + + boolean isBlockStarted = false; + boolean isBlockFinished = false; + + String line; + while (iterator.hasNext()) { + if (linesProcessed++ % LOG_BATCH == 0) { + System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); + stepLineTs = System.currentTimeMillis(); + } + + line = iterator.nextLine(); + + if (isBlockFinished) { + break; + } + + if (!isBlockStarted) { + if (isBlockStarted(line)) { + System.out.println(); + System.out.println(); + System.out.println(line); + System.out.println(); + System.out.println(); + isBlockStarted = true; + } + continue; + } + + if (isBlockFinished(line)) { + isBlockFinished = true; + } else { + try { + List raw = Arrays.stream(line.trim().split("\t")) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); + List values = toValues(raw); + + if (currentWriterCount == 0) { + System.out.println(new Date() + " close writer " + new Date()); + currentTsWriter.close(); + currentTsWriter = WriterBuilder.getLatestWriter(outDir); + } + + if (castStringsIfPossible) { + currentTsWriter.addRow(castToNumericIfPossible(values)); + } else { + currentTsWriter.addRow(values); + } + currentWriterCount++; + if (currentWriterCount >= rowPerFile) { + currentWriterCount = 0; + } + + if (linesMigrated++ % LOG_BATCH == 0) { + System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs)); + stepOkLineTs = System.currentTimeMillis(); + } + } catch (Exception ex) { + System.out.println(ex.getMessage() + " -> " + line); + } + + } + } + + long endTs = System.currentTimeMillis(); + System.out.println(); + System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); + + currentTsWriter.close(); + System.out.println(); + System.out.println("Finished migrate Latest Telemetry"); + } + + + private static List castToNumericIfPossible(List values) { + try { + if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { + Double casted = NumberUtils.createDouble(values.get(6).toString()); + List numeric = Lists.newArrayList(); + numeric.addAll(values); + numeric.set(6, null); + numeric.set(8, casted); + castedOk++; + return numeric; + } + } catch (Throwable th) { + castErrors++; + } + return values; + } + + private static List toValues(List raw) { + //expected Table structure: +// COPY public.ts_kv_latest (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; + + + List result = new ArrayList<>(); + result.add(raw.get(0)); + result.add(fromString(raw.get(1))); + result.add(raw.get(2)); + + long ts = Long.parseLong(raw.get(3)); + result.add(ts); + + result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); + result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); + result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); + result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); + return result; + } + + public static UUID fromString(String src) { + return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" + + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); + } + + private static boolean isBlockStarted(String line) { + return line.startsWith("COPY public.ts_kv_latest"); + } + + private static boolean isBlockFinished(String line) { + return StringUtils.isBlank(line) || line.equals("\\."); + } +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java new file mode 100644 index 0000000000..fd8562ce3a --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.java @@ -0,0 +1,222 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import com.google.common.collect.Lists; +import org.apache.cassandra.io.sstable.CQLSSTableWriter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class PostgresToCassandraTelemetryMigrator { + + private static final long LOG_BATCH = 1000000; + private static final long rowPerFile = 1000000; + + + private static long linesProcessed = 0; + private static long linesMigrated = 0; + private static long castErrors = 0; + private static long castedOk = 0; + + private static long currentWriterCount = 1; + private static CQLSSTableWriter currentTsWriter = null; + private static CQLSSTableWriter currentPartitionWriter = null; + + private static Set partitions = new HashSet<>(); + + + public static void migrateTs(File sourceFile, File outTsDir, File outPartitionDir, boolean castStringsIfPossible) throws IOException { + long startTs = System.currentTimeMillis(); + long stepLineTs = System.currentTimeMillis(); + long stepOkLineTs = System.currentTimeMillis(); + LineIterator iterator = FileUtils.lineIterator(sourceFile); + currentTsWriter = WriterBuilder.getTsWriter(outTsDir); + currentPartitionWriter = WriterBuilder.getPartitionWriter(outPartitionDir); + + boolean isBlockStarted = false; + boolean isBlockFinished = false; + + String line; + while (iterator.hasNext()) { + if (linesProcessed++ % LOG_BATCH == 0) { + System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); + stepLineTs = System.currentTimeMillis(); + } + + line = iterator.nextLine(); + + if (isBlockFinished) { + break; + } + + if (!isBlockStarted) { + if (isBlockStarted(line)) { + System.out.println(); + System.out.println(); + System.out.println(line); + System.out.println(); + System.out.println(); + isBlockStarted = true; + } + continue; + } + + if (isBlockFinished(line)) { + isBlockFinished = true; + } else { + try { + List raw = Arrays.stream(line.trim().split("\t")) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); + List values = toValues(raw); + + if (currentWriterCount == 0) { + System.out.println(new Date() + " close writer " + new Date()); + currentTsWriter.close(); + currentTsWriter = WriterBuilder.getTsWriter(outTsDir); + } + + if (castStringsIfPossible) { + currentTsWriter.addRow(castToNumericIfPossible(values)); + } else { + currentTsWriter.addRow(values); + } + processPartitions(values); + currentWriterCount++; + if (currentWriterCount >= rowPerFile) { + currentWriterCount = 0; + } + + if (linesMigrated++ % LOG_BATCH == 0) { + System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs) + " partitions = " + partitions.size()); + stepOkLineTs = System.currentTimeMillis(); + } + } catch (Exception ex) { + System.out.println(ex.getMessage() + " -> " + line); + } + + } + } + + long endTs = System.currentTimeMillis(); + System.out.println(); + System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); + System.out.println("Partitions collected " + partitions.size()); + + startTs = System.currentTimeMillis(); + for (String partition : partitions) { + String[] split = partition.split("\\|"); + List values = Lists.newArrayList(); + values.add(split[0]); + values.add(UUID.fromString(split[1])); + values.add(split[2]); + values.add(Long.parseLong(split[3])); + currentPartitionWriter.addRow(values); + } + currentPartitionWriter.close(); + endTs = System.currentTimeMillis(); + System.out.println(); + System.out.println(); + System.out.println(new Date() + " Migrated partitions " + partitions.size() + " in " + (endTs - startTs)); + + + currentTsWriter.close(); + System.out.println(); + System.out.println("Finished migrate Telemetry"); + } + + private static List castToNumericIfPossible(List values) { + try { + if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { + Double casted = NumberUtils.createDouble(values.get(6).toString()); + List numeric = Lists.newArrayList(); + numeric.addAll(values); + numeric.set(6, null); + numeric.set(8, casted); + castedOk++; + return numeric; + } + } catch (Throwable th) { + castErrors++; + } + return values; + } + + private static void processPartitions(List values) { + String key = values.get(0) + "|" + values.get(1) + "|" + values.get(2) + "|" + values.get(3); + partitions.add(key); + } + + private static List toValues(List raw) { + //expected Table structure: +// COPY public.ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; + + + List result = new ArrayList<>(); + result.add(raw.get(0)); + result.add(fromString(raw.get(1))); + result.add(raw.get(2)); + + long ts = Long.parseLong(raw.get(3)); + long partition = toPartitionTs(ts); + result.add(partition); + result.add(ts); + + result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); + result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); + result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); + result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); + return result; + } + + public static UUID fromString(String src) { + return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" + + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); + } + + private static long toPartitionTs(long ts) { + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); + return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).toInstant(ZoneOffset.UTC).toEpochMilli(); +// return TsPartitionDate.MONTHS.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); + } + + private static boolean isBlockStarted(String line) { + return line.startsWith("COPY public.ts_kv"); + } + + private static boolean isBlockFinished(String line) { + return StringUtils.isBlank(line) || line.equals("\\."); + } + +} diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md new file mode 100644 index 0000000000..da79f39701 --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md @@ -0,0 +1,62 @@ +# Description: +Tool used for migrating ThingsBoard into hybrid mode from Postgres. +Performance of this tool depends on disk type and instance type (mostly on CPU resources). +But in general here are few benchmarks: +1. Creating Dump of the postgres ts_kv table -> 100GB = 90 minutes +2. If postgres table has size 100GB then dump file will be about 30GB +3. Generation SSTables from dump -> 100GB = 3 hours +4. 100GB Dump file will be converted into SSTable with size about 18GB + +# How to build Tool: +Switch to `tools` project in Command Line and execute +`mvn clean compile assembly:single` +It will generate single jar with dependencies. + +# Instruction: + +1. Dump Telemetry table from the source Postgres table. Do not use compression if possible because Tool can only work with uncompressed file +dump ts_kv table -> `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv > ts_kv.dmp` + +2. Dump Latest Telemetry table from the source Postgres table. Do not use compression if possible because Tool can only work with uncompressed file +dump ts_kv_latest -> `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv_latest > ts_kv_latest.dmp` + +3. [Optional] - move dumped files to the machine where cassandra will be hosted + +4. Prepare directory structure: +Tool will use 3 different directories for saving SSTables - ts_kv_cf, ts_kv_latest_cf, ts_kv_partitions_cf +Create 3 empty directories. For example: + /home/ubunut/migration/ts + /home/ubunut/migration/ts_latest + /home/ubunut/migration/ts_partition + +5. Run tool: +*Note: if you run this tool on remote instance - don't forget to execute this command in screen to avoid unexpected termination +java -jar ./tools-2.4.1-SNAPSHOT-jar-with-dependencies.jar + -latestFrom ./source/ts_kv_latest.dmp + -latestOut /home/ubunut/migration/ts_latest + -tsFrom ./source/ts_kv.dmp + -tsOut /home/ubunut/migration/ts + -partitionsOut /home/ubunut/migration/ts_partition + -castEnable false + + +## After tool finished +1. install Cassandra on the instance +2. Using `cqlsh` create `thingsboard` keyspace and requred tables from this file `schema-ts.cql` +3. Stop Cassandra +4. Copy generated SSTable files into cassandra data dir: + sudo find /home/ubunut/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; + sudo find /home/ubunut/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; + sudo find /home/ubunut/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; + +5. Start Cassandra service and trigger compaction + trigger compactions: nodetool compact thingsboard + check compaction status: nodetool compactionstats + +6. Switch Thignsboard to hybrid mode: +Modify Thingsboard properites file `thingsboard.yml` + - DATABASE_TS_TYPE = cassandra + - TS_KV_PARTITIONING = MONTHS + - [optional] - connection properties for cassandra + +7. Start Thingsboard \ No newline at end of file diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java new file mode 100644 index 0000000000..73be1a7fd1 --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/WriterBuilder.java @@ -0,0 +1,84 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.client.tools.migrator; + +import org.apache.cassandra.io.sstable.CQLSSTableWriter; + +import java.io.File; + +public class WriterBuilder { + + private static final String tsSchema = "CREATE TABLE thingsboard.ts_kv_cf (\n" + + " entity_type text, // (DEVICE, CUSTOMER, TENANT)\n" + + " entity_id timeuuid,\n" + + " key text,\n" + + " partition bigint,\n" + + " ts bigint,\n" + + " bool_v boolean,\n" + + " str_v text,\n" + + " long_v bigint,\n" + + " dbl_v double,\n" + + " PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)\n" + + ");"; + + private static final String latestSchema = "CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (\n" + + " entity_type text, // (DEVICE, CUSTOMER, TENANT)\n" + + " entity_id timeuuid,\n" + + " key text,\n" + + " ts bigint,\n" + + " bool_v boolean,\n" + + " str_v text,\n" + + " long_v bigint,\n" + + " dbl_v double,\n" + + " PRIMARY KEY (( entity_type, entity_id ), key)\n" + + ") WITH compaction = { 'class' : 'LeveledCompactionStrategy' };"; + + private static final String partitionSchema = "CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (\n" + + " entity_type text, // (DEVICE, CUSTOMER, TENANT)\n" + + " entity_id timeuuid,\n" + + " key text,\n" + + " partition bigint,\n" + + " PRIMARY KEY (( entity_type, entity_id, key ), partition)\n" + + ") WITH CLUSTERING ORDER BY ( partition ASC )\n" + + " AND compaction = { 'class' : 'LeveledCompactionStrategy' };"; + + public static CQLSSTableWriter getTsWriter(File dir) { + return CQLSSTableWriter.builder() + .inDirectory(dir) + .forTable(tsSchema) + .using("INSERT INTO thingsboard.ts_kv_cf (entity_type, entity_id, key, partition, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") + .build(); + } + + public static CQLSSTableWriter getLatestWriter(File dir) { + return CQLSSTableWriter.builder() + .inDirectory(dir) + .forTable(latestSchema) + .using("INSERT INTO thingsboard.ts_kv_latest_cf (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + .build(); + } + + public static CQLSSTableWriter getPartitionWriter(File dir) { + return CQLSSTableWriter.builder() + .inDirectory(dir) + .forTable(partitionSchema) + .using("INSERT INTO thingsboard.ts_kv_partitions_cf (entity_type, entity_id, key, partition) " + + "VALUES (?, ?, ?, ?)") + .build(); + } +} From 042db525ae3494573b39aa1e1f643ff4c3f613a0 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Mon, 14 Oct 2019 18:10:20 +0300 Subject: [PATCH 012/120] fix dependencies --- tools/pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/pom.xml b/tools/pom.xml index 16d511e6db..b980123fb2 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -55,13 +55,10 @@ org.apache.cassandra cassandra-all 3.11.4 - compile commons-io commons-io - 2.6 - compile From 09a164be0fde68183f24f3082753e4e504df3e51 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Tue, 15 Oct 2019 10:57:47 +0300 Subject: [PATCH 013/120] Migrator tool: fix dependencies and update docs --- tools/pom.xml | 4 + .../tools/migrator/PgCaLatestMigrator.java | 2 +- .../client/tools/migrator/README.md | 73 +++++++++++++------ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/tools/pom.xml b/tools/pom.xml index b980123fb2..0d044365a0 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -56,6 +56,10 @@ cassandra-all 3.11.4 + + com.datastax.cassandra + cassandra-driver-core + commons-io commons-io diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java index 667f5e6d0f..a109b0a5dd 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md index da79f39701..70c5dafcaf 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md @@ -1,5 +1,6 @@ # Description: -Tool used for migrating ThingsBoard into hybrid mode from Postgres. +This tool used for migrating ThingsBoard into hybrid mode from Postgres. + Performance of this tool depends on disk type and instance type (mostly on CPU resources). But in general here are few benchmarks: 1. Creating Dump of the postgres ts_kv table -> 100GB = 90 minutes @@ -7,56 +8,80 @@ But in general here are few benchmarks: 3. Generation SSTables from dump -> 100GB = 3 hours 4. 100GB Dump file will be converted into SSTable with size about 18GB -# How to build Tool: -Switch to `tools` project in Command Line and execute -`mvn clean compile assembly:single` -It will generate single jar with dependencies. +# Tool build Instruction: +Switch to `tools` module in Command Line and execute -# Instruction: + mvn clean compile assembly:single + +It will generate single jar file with all required dependencies inside `target dir` -> `tools-2.4.1-SNAPSHOT-jar-with-dependencies.jar`. + + +# Prepare requred files and run Tool: + +#### Dump data from the source Postgres Database +*Do not use compression if possible because Tool can only work with uncompressed file + +1. Dump table `ts_kv` table: + + `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv > ts_kv.dmp` + +2. Dump table `ts_kv_latest` table: -1. Dump Telemetry table from the source Postgres table. Do not use compression if possible because Tool can only work with uncompressed file -dump ts_kv table -> `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv > ts_kv.dmp` + `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv_latest > ts_kv_latest.dmp` -2. Dump Latest Telemetry table from the source Postgres table. Do not use compression if possible because Tool can only work with uncompressed file -dump ts_kv_latest -> `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv_latest > ts_kv_latest.dmp` +3. [Optional] move table dumps to the instance where cassandra will be hosted -3. [Optional] - move dumped files to the machine where cassandra will be hosted +#### Prepare directory structure for SSTables +Tool use 3 different directories for saving SSTables - `ts_kv_cf`, `ts_kv_latest_cf`, `ts_kv_partitions_cf` -4. Prepare directory structure: -Tool will use 3 different directories for saving SSTables - ts_kv_cf, ts_kv_latest_cf, ts_kv_partitions_cf Create 3 empty directories. For example: + /home/ubunut/migration/ts /home/ubunut/migration/ts_latest /home/ubunut/migration/ts_partition -5. Run tool: -*Note: if you run this tool on remote instance - don't forget to execute this command in screen to avoid unexpected termination +#### Run tool +*Note: if you run this tool on remote instance - don't forget to execute this command in `screen` to avoid unexpected termination + +``` java -jar ./tools-2.4.1-SNAPSHOT-jar-with-dependencies.jar - -latestFrom ./source/ts_kv_latest.dmp + -latestFrom ./source/ts_kv_latest.dmp -latestOut /home/ubunut/migration/ts_latest -tsFrom ./source/ts_kv.dmp -tsOut /home/ubunut/migration/ts -partitionsOut /home/ubunut/migration/ts_partition - -castEnable false + -castEnable false +``` +Tool execution time depends on DB size, CPU resources and Disk throughput -## After tool finished -1. install Cassandra on the instance -2. Using `cqlsh` create `thingsboard` keyspace and requred tables from this file `schema-ts.cql` +## Adding SSTables into Cassandra +* Note that this this part works only for single node Cassandra Cluster. If you have more nodes - it is better to use `sstableloader` tool. + +1. [Optional] install Cassandra on the instance +2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this file `schema-ts.cql` 3. Stop Cassandra 4. Copy generated SSTable files into cassandra data dir: + +``` sudo find /home/ubunut/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; sudo find /home/ubunut/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; sudo find /home/ubunut/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; +``` 5. Start Cassandra service and trigger compaction + +``` trigger compactions: nodetool compact thingsboard check compaction status: nodetool compactionstats +``` -6. Switch Thignsboard to hybrid mode: +## Switch Thignsboard into Hybrid Mode + Modify Thingsboard properites file `thingsboard.yml` + - DATABASE_TS_TYPE = cassandra - - TS_KV_PARTITIONING = MONTHS - - [optional] - connection properties for cassandra + - TS_KV_PARTITIONING = MONTHS -7. Start Thingsboard \ No newline at end of file +# Final steps +Start Thingsboard and verify migration \ No newline at end of file From 26fa1fd832972aef0a88a1caf6a0a6dacdf5cd0b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 15 Oct 2019 13:23:43 +0300 Subject: [PATCH 014/120] Improve widget device claim --- .../json/system/widget_bundles/input_widgets.json | 14 +++++++------- ui/src/app/locale/locale.constant-en_US.json | 8 ++++++++ ui/src/app/locale/locale.constant-ru_RU.json | 10 +++++++++- ui/src/app/locale/locale.constant-uk_UA.json | 8 ++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index c6df5a1fdb..47e25203e4 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -347,15 +347,15 @@ "descriptor": { "type": "static", "sizeX": 7.5, - "sizeY": 4, + "sizeY": 4.5, "resources": [], - "templateHtml": "
\n
\n \n \n \n
\n
Device name is required.
\n
\n
\n \n \n \n
\n
Device secret is required.
\n
\n
\n
\n Claim\n
", - "templateCss": ".claim-form {\n margin: 16px;\n}", - "controllerScript": "let $scope;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n let $injector = $scope.$injector;\n let $q = $injector.get('$q');\n let $http = $injector.get('$http');\n let toast = $scope.$injector.get('toast');\n $scope.deviceSecretField = self.ctx.settings.deviceSecret;\n $scope.deviceObj = {};\n \n $scope.claim = () => {\n $scope.loading = true;\n claimDevice($scope.deviceObj.deviceName, $scope.deviceObj.deviceSecret).then(\n (data) => {\n resetForm();\n $scope.claimDeviceForm.$setPristine();\n $scope.claimDeviceForm.$setUntouched();\n $scope.loading = false;\n if (data.response == \"SUCCESS\") {\n toast.showSuccess('Device was successfully claimed!', 2000, angular.element('.claim-form'), 'bottom left');\n }\n },\n () => {\n $scope.loading = false;\n }\n );\n }\n \n function claimDevice(deviceName, deviceSecret, config) {\n let deferred = $q.defer();\n let url = \"/api/customer/device/\" + deviceName + \"/claim\";\n let obj = deviceSecret ? { secretKey: deviceSecret } : {};\n $http.post(url, obj, config).then(\n (payload) => {\n deferred.resolve(payload.data); \n },\n () => {\n deferred.reject();\n }\n );\n return deferred.promise;\n }\n \n function resetForm() {\n $scope.deviceObj = {};\n }\n}\n\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [],\n \"properties\": {\n \"deviceSecret\": {\n \"title\": \"Add 'Device secret' field\",\n \"type\": \"boolean\",\n \"default\": false\n }\n }\n },\n \"form\": [\n \"deviceSecret\"\n ]\n}", + "templateHtml": "
\n
\n \n \n \n
\n
{{requiredErrorDevice}}
\n
\n
\n \n \n \n
\n
{{requiredErrorSecretKey}}
\n
\n
\n
\n
\n {{labelClaimButon}}\n
\n
", + "templateCss": ".claim-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}", + "controllerScript": "let $scope;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n let $injector = $scope.$injector;\n let $q = $injector.get('$q');\n let $http = $injector.get('$http');\n let toast = $scope.$injector.get('toast');\n let utils = $scope.$injector.get('utils');\n let $translate = $scope.$injector.get('$translate');\n let $rootScope = $scope.$injector.get('$rootScope');\n let settings = self.ctx.settings || {};\n $scope.secretKeyField = settings.deviceSecret;\n $scope.showLabel = settings.showLabel;\n $scope.deviceObj = {};\n \n const config = {\n ignoreErrors: true \n };\n \n let titleTemplate = \"\";\n let successfulClaim = utils.customTranslation(settings.successfulClaimDevice, settings.successfulClaimDevice) || $translate.instant('widgets.input-widgets.claim-successful');\n let failedClaimDevice = utils.customTranslation(settings.failedClaimDevice, settings.failedClaimDevice) || $translate.instant('widgets.input-widgets.claim-failed');\n let deviceNotFound = utils.customTranslation(settings.deviceNotFound, settings.deviceNotFound) || $translate.instant('widgets.input-widgets.claim-not-found');\n \n if (settings.widgetTitle && settings.widgetTitle.length) {\n titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n titleTemplate = self.ctx.widgetConfig.title;\n }\n self.ctx.widgetTitle = titleTemplate;\n \n $scope.deviceLabel = utils.customTranslation(settings.deviceLabel, settings.deviceLabel) || $translate.instant('widgets.input-widgets.device-name');\n $scope.requiredErrorDevice= utils.customTranslation(settings.requiredErrorDevice, settings.requiredErrorDevice) || $translate.instant('widgets.input-widgets.device-name-required');\n \n $scope.secretKeyLabel = utils.customTranslation(settings.secretKeyLabel, settings.secretKeyLabel) || $translate.instant('widgets.input-widgets.secret-key');\n $scope.requiredErrorSecretKey= utils.customTranslation(settings.requiredErrorSecretKey, settings.requiredErrorSecretKey) || $translate.instant('widgets.input-widgets.secret-key-required');\n \n $scope.labelClaimButon = utils.customTranslation(settings.labelClaimButon, settings.labelClaimButon) || $translate.instant('widgets.input-widgets.claim-device');\n \n $scope.claim = () => {\n $scope.loading = true;\n claimDevice($scope.deviceObj.deviceName, $scope.deviceObj.deviceSecret, config).then(\n (data) => {\n successClaim();\n },\n (error) => {\n $scope.loading = false;\n if(error.status == 404) {\n toast.showError(deviceNotFound, angular.element('.claim-form'),'bottom left');\n } else if(error.status == 400) {\n toast.showError(failedClaimDevice, angular.element('.claim-form'),'bottom left');\n }\n }\n );\n }\n \n function claimDevice(deviceName, deviceSecret, config) {\n let deferred = $q.defer();\n let url = \"/api/customer/device/\" + deviceName + \"/claim\";\n let obj = deviceSecret ? { secretKey: deviceSecret } : {};\n $http.post(url, obj, config).then(\n (payload) => {\n deferred.resolve(payload.data); \n },\n (error) => {\n deferred.reject(error);\n }\n );\n return deferred.promise;\n }\n \n function updateAliasData() {\n var aliasIds = [];\n for (var id in self.ctx.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n var tasks = [];\n aliasIds.forEach(function(aliasId) {\n self.ctx.aliasController.setAliasUnresolved(aliasId);\n tasks.push(self.ctx.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(function() {\n $rootScope.$broadcast('widgetForceReInit');\n });\n }\n \n function successClaim() {\n resetForm();\n $scope.claimDeviceForm.$setPristine();\n $scope.claimDeviceForm.$setUntouched();\n $scope.loading = false;\n toast.showSuccess(successfulClaim, 2000);\n updateAliasData();\n }\n \n function resetForm() {\n $scope.deviceObj = {};\n }\n}\n\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"deviceSecret\": {\n \"title\": \"Show 'Secret key' input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"deviceLabel\": {\n \"title\": \"Label for device name\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorDevice\": {\n \"title\": \"'Device name required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"secretKeyLabel\": {\n \"title\": \"Label for secret key\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorSecretKey\": {\n \"title\": \"'Secret key required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"labelClaimButon\": {\n \"title\": \"Label for claiming button\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"successfulClaimDevice\": {\n \"title\": \"Text message of successful device claiming\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"deviceNotFound\": {\n \"title\": \"Text message when device not found\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"failedClaimDevice\": {\n \"title\": \"Text message of failed device claiming\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n [\n \"widgetTitle\",\n \"labelClaimButon\",\n \"deviceSecret\",\n \"showLabel\",\n \"deviceLabel\",\n \"secretKeyLabel\"\n ],\n [\n \"deviceNotFound\",\n \"failedClaimDevice\",\n \"successfulClaimDevice\",\n \"requiredErrorDevice\",\n \"requiredErrorSecretKey\"\n ]\n ],\n \"groupInfoes\": [{\n \"formIndex\": 0,\n \"GroupTitle\": \"General settings\"\n }, {\n \"formIndex\": 1,\n \"GroupTitle\": \"Message settings\"\n }]\n}", "dataKeySettingsSchema": "{}\n", - "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"deviceSecret\":true},\"title\":\"Device claiming widget\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"enableDataExport\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"deviceSecret\":true,\"showLabel\":true},\"title\":\"Device claiming widget\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"enableDataExport\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } } ] -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 006db27e14..42a34cbb46 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1699,7 +1699,13 @@ }, "input-widgets": { "attribute-not-allowed": "Attribute parameter cannot be used in this widget", + "claim-device": "Claim device", + "claim-failed": "Failed to claim the device!", + "claim-not-found": "Device not found!", + "claim-successful": "Device was successfully claimed!", "date": "Date", + "device-name": "Device name", + "device-name-required": "Device name is required", "discard-changes": "Discard changes", "entity-attribute-required": "Entity attribute is required", "entity-timeseries-required": "Entity timeseries is required", @@ -1710,6 +1716,8 @@ "no-image": "No image", "no-support-web-camera": "No supported web camera", "no-timeseries-selected": "No timeseries selected", + "secret-key": "Secret key", + "secret-key-required": "Secret key is required", "switch-attribute-value": "Switch entity attribute value", "switch-camera": "Switch camera", "switch-timeseries-value": "Switch entity timeseries value", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index c7d090eefa..b79be83801 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1615,13 +1615,21 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете", + "claim-device": "Подтвердить устройство", + "claim-failed": "Не удалось подтвердить устройство!", + "claim-not-found": "Устройство не найдено!", + "claim-successful": "Устройство успешно подтверждено!", "discard-changes": "Отменить изменения", + "device-name": "Название устройства", + "device-name-required": "Необходимо указать название устройства", "entity-attribute-required": "Значение атрибута обязателено", "entity-timeseries-required": "Значение телеметрии обязательно", "not-allowed-entity": "Выбраный объект не имеет общих атрибутов", "no-attribute-selected": "Атрибут не выбран", "no-entity-selected": "Объект не выбран", "no-timeseries-selected": "Параметр телеметрии не выбран", + "secret-key": "Секретный ключ", + "secret-key-required": "Необходимо указать секретный ключ", "switch-attribute-value": "Изменить значение атрибута", "switch-timeseries-value": "Изменить значение телеметрии", "timeseries-not-allowed": "Телеметрия не может быть выбрана в этом виджете", @@ -1665,4 +1673,4 @@ "cs_CZ": "Чешский" } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index ad8b5682fa..d0c15734bd 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -2182,13 +2182,21 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не може бути вибраний в цьому віджеті", + "claim-device": "Підтвердити пристрій", + "claim-failed": "Не вдалося підтвердити пристрій!", + "claim-not-found": "Пристрій не знайдено!", + "claim-successful": "Пристрій успішно підтверджено!", "discard-changes": "Скасувати зміни", + "device-name": "Назва пристрою", + "device-name-required": "Необхідно вказати назву пристрою", "entity-attribute-required": "Значення атрибута обов'язкове", "entity-timeseries-required": "Значення телеметрії обов'язкове", "not-allowed-entity": "Обрана сутність не має спільних атрибутів", "no-attribute-selected": "Атрибут не вибрано", "no-entity-selected": "Сутність не вибрано", "no-timeseries-selected": "Параметр телеметрії не вибрано", + "secret-key": "Секретний ключ", + "secret-key-required": "Необхідно вказати секретний ключ", "switch-attribute-value": "Змінити значення атрибута", "switch-timeseries-value": "Змінити значення телеметрії", "timeseries-not-allowed": "Телеметрія не може бути вибрана в цьому віджеті", From 502300cedde4def83b3119ee4e69285e6e793434 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 15 Oct 2019 13:57:22 +0300 Subject: [PATCH 015/120] License Header Fix --- .../thingsboard/client/tools/migrator/PgCaLatestMigrator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java index a109b0a5dd..667f5e6d0f 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, From ddce20ac01ed40bc7f97f6095508812aa0dee5e1 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 10 Oct 2019 11:45:01 +0300 Subject: [PATCH 016/120] Missed translations for RU & UA --- ui/src/app/locale/locale.constant-en_US.json | 12 +- ui/src/app/locale/locale.constant-ru_RU.json | 87 ++++-- ui/src/app/locale/locale.constant-uk_UA.json | 283 +++++++++++-------- 3 files changed, 246 insertions(+), 136 deletions(-) diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 42a34cbb46..4eb0d9b7aa 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -822,7 +822,7 @@ "no-keys-found": "No keys found.", "create-new-alias": "Create a new one!", "create-new-key": "Create a new one!", - "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique whithin the dashboard.", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique within the dashboard.", "configure-alias": "Configure '{{alias}}' alias", "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", "alias": "Alias", @@ -845,21 +845,21 @@ "add-entity-view-text": "Add new entity view", "delete": "Delete entity view", "assign-entity-views": "Assign entity views", - "assign-entity-views-text": "Assign { count, plural, 1 {1 entityView} other {# entityViews} } to customer", + "assign-entity-views-text": "Assign { count, plural, 1 {1 entity view} other {# entity views} } to customer", "delete-entity-views": "Delete entity views", "unassign-from-customer": "Unassign from customer", "unassign-entity-views": "Unassign entity views", - "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entityView} other {# entityViews} } from customer", + "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entity view} other {# entity views} } from customer", "assign-new-entity-view": "Assign new entity view", "delete-entity-view-title": "Are you sure you want to delete the entity view '{{entityViewName}}'?", "delete-entity-view-text": "Be careful, after the confirmation the entity view and all related data will become unrecoverable.", - "delete-entity-views-title": "Are you sure you want to entity view { count, plural, 1 {1 entityView} other {# entityViews} }?", - "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entityView} other {# entityViews} }", + "delete-entity-views-title": "Are you sure you want to delete { count, plural, 1 {1 entity view} other {# entity views} }?", + "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entity view} other {# entity views} }", "delete-entity-views-text": "Be careful, after the confirmation all selected entity views will be removed and all related data will become unrecoverable.", "unassign-entity-view-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?", "unassign-entity-view-text": "After the confirmation the entity view will be unassigned and won't be accessible by the customer.", "unassign-entity-view": "Unassign entity view", - "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entityView} other {# entityViews} }?", + "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entity view} other {# entity views} }?", "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.", "entity-view-type": "Entity View type", "entity-view-type-required": "Entity View type is required.", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index b79be83801..9f1d582e77 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -48,7 +48,9 @@ "paste-reference": "Вставить ссылку", "import": "Импортировать", "export": "Экспортировать", - "share-via": "Поделиться в {{provider}}" + "share-via": "Поделиться в {{provider}}", + "continue": "Продолжить", + "discard-changes": "Отменить изменения" }, "aggregation": { "aggregation": "Агрегация", @@ -150,8 +152,8 @@ "filter-type-single-entity": "Отдельный объект", "filter-type-entity-list": "Список объектов", "filter-type-entity-name": "Название объекта", - "filter-type-state-entity": "Объект, полученный из дашборда", - "filter-type-state-entity-description": "Объект, полученный из параметров дашборда", + "filter-type-state-entity": "Объект из состояния дашборда", + "filter-type-state-entity-description": "Объект, полученный из параметров состояния дашборда", "filter-type-asset-type": "Тип актива", "filter-type-asset-type-description": "Активы типа '{{assetType}}'", "filter-type-asset-type-and-name-description": "Активы типа '{{assetType}}' и названием, начинающимся с '{{prefix}}'", @@ -246,7 +248,9 @@ "select-asset": "Выбрать активы", "no-assets-matching": "Активы, соответствующие '{{entity}}', не найдены.", "asset-required": "Актив обязателен", - "name-starts-with": "Название актива, начинающееся с" + "name-starts-with": "Название актива, начинающееся с", + "import": "Импортировать активы", + "asset-file": "Файл с активами" }, "attribute": { "attributes": "Атрибуты", @@ -665,7 +669,9 @@ "is-gateway": "Гейтвей", "public": "Общедоступный", "device-public": "Устройство общедоступно", - "select-device": "Выбрать устройство" + "select-device": "Выбрать устройство", + "import": "Импортировать устройства", + "device-file": "Файл с устройствами" }, "dialog": { "close": "Закрыть диалог" @@ -771,6 +777,7 @@ "search": "Поиск объектов", "selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }", "entity-name": "Название объекта", + "entity-label": "Метка объекта", "details": "Подробности об объекте", "no-entities-prompt": "Объекты не найдены", "no-data": "Нет данных для отображения", @@ -793,7 +800,7 @@ "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.
В рамках одного дашборда псевдонимы представлений объектов должны быть уникальными.", "configure-alias": "Настроить псевдоним '{{alias}}'", "no-entity-views-matching": "Объекты, соответствующие '{{entity}}', не найдены.", - "alias": "Псевдонимы", + "alias": "Псевдоним", "alias-required": "Псевдоним представления объекта обязателен.", "remove-alias": "Убрать псевдоним представления объекта", "add-alias": "Добавить псевдоним представления объекта", @@ -843,11 +850,12 @@ "events": "События", "details": "Подробности", "copyId": "Копировать ИД представление объекта", - "assignedToCustomer": "Назначенные клиенту", + "assignedToCustomer": "Назначено клиенту", "unable-entity-view-device-alias-title": "Не удалось удалить псевдоним представления объекта", "unable-entity-view-device-alias-text": "Не удалось удалить псевдоним устройства '{{entityViewAlias}}', т.к. он используется следующими виджетами:
{{widgetsList}}", "select-entity-view": "Выбрать представление объекта", "make-public": "Открыть общий доступ к представлению объекта", + "make-private": "Закрыть общий доступ к представлению объекта", "start-date": "Дата начала", "start-ts": "Время начала", "end-date": "Дата окончания", @@ -865,7 +873,11 @@ "attributes-propagation": "Пробросить атрибуты", "attributes-propagation-hint": "Представление объекта автоматически копирует указанные атрибуты с Целевого Объекта каждый раз, когда вы сохраняете или обновляете это представление. В целях производительности атрибуты целевого объекта не пробрасываются в представление объекта на каждом их изменении. Вы можете включить автоматический проброс, настроив в вашей цепочке правило \"copy to view\" и соединив его с сообщениями типа \"Post attributes\" и \"Attributes Updated\".", "timeseries-data": "Данные телеметрии", - "timeseries-data-hint": "Настроить ключи данных телеметрии целевого объекта, которые будут доступны представлению объекта. Эти данные только для чтения." + "timeseries-data-hint": "Настроить ключи данных телеметрии целевого объекта, которые будут доступны представлению объекта. Эти данные только для чтения.", + "make-public-entity-view-title": "Вы уверенны, что хотите открыть общий доступ к представленю объекта '{{entityViewName}}'?", + "make-public-entity-view-text": "После подтверждения представление объекта и все связанные с ним данные станут публичными и доступными для других пользователей.", + "make-private-entity-view-title": "Вы уверенны, что хотите закрыть общий доступ к представлению объекта '{{entityViewName}}'?", + "make-private-entity-view-text": "После подтверждения представление объекта и все звязанные с ним данные станут приватными и не будут доступны для других пользователей." }, "event": { "event-type": "Тип события", @@ -1012,6 +1024,7 @@ "modbus-add-server": "Добавить сервер/ведомое устройство", "modbus-add-server-prompt": "Пожалуйста, добавить сервер/ведомое устройство", "modbus-transport": "Транспорт", + "modbus-tcp-reconnect": "Переподключатсься автоматически", "modbus-port-name": "Название последовательного порта", "modbus-encoding": "Кодирование символов", "modbus-parity": "Паритет", @@ -1086,7 +1099,40 @@ }, "import": { "no-file": "Файл не выбран", - "drop-file": "Перетащите JSON файл или кликните для выбора файла." + "drop-file": "Перетащите JSON файл или кликните для выбора файла.", + "drop-file-csv": "Перетащите CSV файл или кликните для выбора файла.", + "column-value": "Значение", + "column-title": "Название", + "column-example": "Пример значений данных", + "column-key": "Ключ атрибута/телеметрии", + "csv-delimiter": "Разделитель в CSV файле", + "csv-first-line-header": "Первая строка содержит названия колонок", + "csv-update-data": "Обновить атрибут/телеметрию", + "import-csv-number-columns-error": "Файл должен содержать как минимум две колонки", + "import-csv-invalid-format-error": "Неверный формат данных. Строка: '{{line}}'", + "column-type": { + "name": "Название", + "type": "Тип", + "column-type": "Тип колонки", + "client-attribute": "Клиентский атрибут", + "shared-attribute": "Общий атрибут", + "server-attribute": "Серверный атрибут", + "timeseries": "Телеметрия", + "entity-field": "Entity field", + "access-token": "Токен" + }, + "stepper-text": { + "select-file": "Выберите файл", + "configuration": "Конфигурация импорта", + "column-type": "Выберите тип колонок", + "creat-entities": "Создание новых объектов", + "done": "Завершено" + }, + "message": { + "create-entities": "{{count}} новый(х) объект(ов) было успешно создано.", + "update-entities": "{{count}} объект(ов) успешно обновлено.", + "error-entities": "Возникла ошибка при создании {{count}} объекта(ов)." + } }, "item": { "selected": "Выбранные" @@ -1347,7 +1393,8 @@ "edit": "Изменить временное окно", "date-range": "Диапазон дат", "last": "Последние", - "time-period": "Период времени" + "time-period": "Период времени", + "hide": "Скрыть" }, "user": { "user": "Пользователь", @@ -1419,12 +1466,12 @@ "edit": "Редактировать виджет", "remove-widget-title": "Вы точно хотите удалить виджет '{{widgetTitle}}'?", "remove-widget-text": "Внимание, после подтверждения виджет и все связанные с ним данные будут безвозвратно утеряны.", - "timeseries": "Выборка по времени", - "search-data": "Search data", - "no-data-found": "No data found", + "timeseries": "Телеметрия", + "search-data": "Поиск данных", + "no-data-found": "Данные не найдено", "latest-values": "Последние значения", "rpc": "Управляющий виджет", - "alarm": "Alarm widget", + "alarm": "Виджет оповещений", "static": "Статический виджет", "select-widget-type": "Выберите тип виджета", "missing-widget-title-error": "Укажите название виджета!", @@ -1521,6 +1568,7 @@ "decimals": "Количество цифр после запятой", "timewindow": "Временное окно", "use-dashboard-timewindow": "Использовать временное окно дашборда", + "display-timewindow": "Показывать временное окно", "display-legend": "Показать легенду", "datasources": "Источники данных", "maximum-datasources": "Максимальной количество источников данных равно {{count}}", @@ -1545,7 +1593,10 @@ "edit-action": "Редактировать действие", "delete-action": "Удалить действие", "delete-action-title": "Удалить действие виджета", - "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?" + "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?", + "display-icon": "Показывать иконку в названии", + "icon-color": "Цвет иконки", + "icon-size": "Размер иконки" }, "widget-type": { "import": "Импортировать тип виджета", @@ -1648,11 +1699,11 @@ }, "custom": { "widget-action": { - "action-cell-button": "Кнопка действия ячейки", + "action-cell-button": "Кнопка действия в ячейке таблицы", "row-click": "Действий при щелчке на строку", - "marker-click": "Действия при щелчке на указателе", + "marker-click": "Действия при щелчке на маркер", "polygon-click": "Действия при щелчке на полигон", - "tooltip-tag-action": "Действие при подсказке" + "tooltip-tag-action": "Действие при нажатии на ссылку в подсказке" } }, "language": { diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index d0c15734bd..df720c6cf8 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -49,6 +49,8 @@ "import": "Імпортувати", "export": "Експортувати", "share-via": "Поділитися через {{provider}}", + "continue": "Продовжити", + "discard-changes": "Скасувати зміни", "move": "Перемістити", "select": "Вибрати" }, @@ -193,20 +195,20 @@ "filter-type": "Тип фільтра", "filter-type-required": "Необхідно вказати тип фільтра.", "entity-filter-no-entity-matched": "Не знайдено жодних сутностей, які відповідають вказаному фільтру.", - "no-entity-filter-specified": "No entity filter specified", - "root-state-entity": "Use dashboard state entity as root", - "group-state-entity": "Use dashboard state entity as entity group", - "root-entity": "Root entity", - "state-entity-parameter-name": "State entity parameter name", - "default-state-entity": "Default state entity", - "default-state-entity-group": "Default state entity group", - "default-entity-parameter-name": "By default", - "max-relation-level": "Max relation level", - "unlimited-level": "Unlimited level", - "state-entity": "Dashboard state entity", - "entities-of-group-state-entity": "Entities from dashboard state entity group", - "all-entities": "All entities", - "any-relation": "any" + "no-entity-filter-specified": "Фільтр обїектів не вказано", + "root-state-entity": "Використовувати сутінсть стану як кореневу", + "group-state-entity": "Використовувати групу сутностей стану як кореневу", + "root-entity": "Коренева сутність", + "state-entity-parameter-name": "Параметр сутності стану", + "default-state-entity": "Сутність стану за замовчуванням", + "default-state-entity-group": "Група сутностей стану за замовчуванням", + "default-entity-parameter-name": "За замовчуванням", + "max-relation-level": "Максимальна глибина відносин", + "unlimited-level": "Необмежена глибина", + "state-entity": "Сутність стану панелі пристроїв", + "entities-of-group-state-entity": "Сутності із групи сутностей стану", + "all-entities": "Всі сутності", + "any-relation": "не вказано" }, "asset": { "asset": "Актив", @@ -276,7 +278,9 @@ "remove-assets-from-group": "Ви впевнені, що хочете видалити { count, plural, 1 {1 актив} other {# актив} } з групи '{entityGroup}'?", "group": "Група активів", "list-of-groups": "{ count, plural, 1 {Одна група активів} other {Список # груп активів} }", - "group-name-starts-with": "Групи активів, чиї імена починаються з '{{prefix}}'" + "group-name-starts-with": "Групи активів, чиї імена починаються з '{{prefix}}'", + "import": "Імпортувати активи", + "asset-file": "Файл з активами" }, "attribute": { "attributes": "Атрибути", @@ -400,8 +404,8 @@ "encoder": "Кодер", "test-decoder-fuction": "Тестування функції декодера", "test-encoder-fuction": "Тестування функції кодера", - "decoder-input-params": "Параметри введення декодера", - "encoder-input-params": "Параметри введення кодера", + "decoder-input-params": "Вхідні параметри декодера", + "encoder-input-params": "Вхідні параметри кодера", "payload": "Вхідне повідомлення", "payload-content-type": "Тип контенту вхідного повідомлення", "payload-content": "Зміст вхідного повідомлення", @@ -411,8 +415,8 @@ "test": "Тест", "metadata": "Метадані", "metadata-required": "Записи метаданих не можуть бути порожніми.", - "integration-metadata": "Інтеграція метаданих", - "integration-metadata-required": "Записи інтеграції метаданих не можуть бути порожніми.", + "integration-metadata": "Метедані інтеграції", + "integration-metadata-required": "Параметри метаданих інтеграції не можуть бути порожніми.", "output": "Вихідні дані", "import": "Імпорт перетворювача даних", "export": "Експорт перетворювача даних", @@ -482,7 +486,7 @@ "group": "Група клієнтів", "list-of-groups": "{ count, plural, 1 {Одна група клієнтів} other {Список # груп клієнтів} }", "group-name-starts-with": "Групи клієнтів, імена яких починаються з '{{prefix}}'", - "select-default-customer": "Виберати клієнта за замовчуванням", + "select-default-customer": "Вибрати клієнта за замовчуванням", "default-customer": "Клієнт за замовчуванням", "default-customer-required": "Необхідно вказати клієнта за замовчуванням для налагодження панелі візуалізації на рівні замовника", "allow-white-labeling": "Дозволити брендування" @@ -650,7 +654,7 @@ "show-details": "Показати деталі", "hide-details": "Приховати деталі", "select-state": "Виберіть цільовий стан", - "state-controller": "Контроль стану" + "state-controller": "Контроллер стану" }, "datakey": { "settings": "Налаштування", @@ -783,6 +787,8 @@ "public": "Публічно", "device-public": "Пристрій є публічним", "select-device": "Виберіть пристрій", + "import": "Імпортувати пристрої", + "device-file": "Файл з пристроями", "selected-devices": "{ count, plural, 1 {1 пристрій} other {# пристрої} } вибрано", "search": "Шукати пристрої", "select-group-to-add": "Виберіть цільову групу, щоб додати вибраний пристрій", @@ -884,9 +890,9 @@ "type-alarms": "Сигнали тривоги", "list-of-alarms": "{ count, plural, 1 {Один сигнал тривоги} other {Список # сигналів тривоги} }", "alarm-name-starts-with": "Сигнали тривоги, імена яких починаються '{{prefix}}'", - "type-rulechain": "Правило ланцюжка", - "type-rulechains": "Правило ланцюжків", - "list-of-rulechains": "{ count, plural, 1 {Одне правило ланцюжка} other {Список # правил ланцюжків} }", + "type-rulechain": "Ланцюжок правил", + "type-rulechains": "Ланцюжки правил", + "list-of-rulechains": "{ count, plural, 1 {Один ланцюжок правил} other {Список # ланцюжків правил} }", "rulechain-name-starts-with": "Правило ланцюжків, імена яких починаються '{{prefix}}'", "type-scheduler-event": "Scheduler event", "type-scheduler-events": "Scheduler events", @@ -904,6 +910,7 @@ "search": "Пошук сутностей", "selected-entities": "{ count, plural, 1 {1 сутність} other {# сутності} } вибрано", "entity-name": "Ім'я сутності", + "entity-label": "Мітка сутності", "details": "Подробиці сутності", "no-entities-prompt": "Сутності не знайдено", "no-data": "Немає даних для відображення", @@ -966,7 +973,7 @@ }, "column-type-required": "Необхідно вказати тип стовпця.", "entity-field": { - "created-time": "Створений час", + "created-time": "Час створення", "name": "Ім'я", "type": "Тип", "assigned_customer": "Призначений клієнт", @@ -986,7 +993,7 @@ "sort-order": { "asc": "У порядку зростання", "desc": "У порядку зменшення", - "none": "Не має" + "none": "Немає" }, "details-mode": { "on-row-click": "Клацніть на рядок", @@ -995,7 +1002,7 @@ }, "add-to-group": "Додати до групи", "move-to-group": "Перемістити до групи", - "select-entity-group": "Виберати групу сутностей", + "select-entity-group": "Виберіть групу сутностей", "no-entity-groups-matching": "Не знайдено жодних груп сутностей, що відповідають '{{entityGroup}}'.", "target-entity-group-required": "Необхідно вказати цільову групу сутності.", "remove-from-group": "Видалити з групи", @@ -1013,7 +1020,7 @@ "enable-assets-management": "Увімкнути керування активами", "enable-devices-management": "Увімкнути керування пристроями", "enable-dashboards-management": "Увімкнути керування панелями візуалізації", - "open-details-on": "Відкрити деталі сутності", + "open-details-on": "Відкрити деталі сутності по", "select-existing": "Виберіть існуючу групу сутностей", "create-new": "Створити нову групу сутностей", "new-entity-group-name": "Нове ім'я групи сутностей", @@ -1023,77 +1030,78 @@ "entity-group-name-filter-required": "Необхідно задати назву групи сутностей." }, "entity-view": { - "entity-view": "Перегляд сутності", - "entity-view-required": "Необхідно вказати перегляд сутності.", - "entity-views": "Перегляди сутностей", - "management": "Керування переглядом сутностей", - "view-entity-views": "Переглянути перегляд сутностей", - "entity-view-alias": "Псевдонім перегляду сутності", - "aliases": "Псевдоніми перегляду сутності", + "entity-view": "Представлення сутності", + "entity-view-required": "Необхідно вказати представлення сутності.", + "entity-views": "Представлення сутностей", + "management": "Керування представленням сутностей", + "view-entity-views": "Переглянути представлення сутностей", + "entity-view-alias": "Псевдонім представлення сутності", + "aliases": "Псевдоніми представлення сутності", "no-alias-matching": "Псевдонім'{{alias}}' не знайдено.", "no-aliases-found": "Псевдоніми не знайдено.", "no-key-matching": "'Ключ {{key}}' не знайдено.", "no-keys-found": "Ключі не знайдено.", "create-new-alias": "Створити новий!", "create-new-key": "Створити новий!", - "duplicate-alias-error": "Псевдонім з такою назвою вже існує '{{alias}}'.
Псевдоніми перегляду повинні бути унікальними на панелі візуалізації.", + "duplicate-alias-error": "Псевдонім з такою назвою вже існує '{{alias}}'.
Псевдоніми представлення повинні бути унікальними на панелі візуалізації.", "configure-alias": "Налаштувати псевдонім '{{alias}}'", "no-entity-views-matching": "Сутності, які відповідають '{{entity}}' не знайдені.", "alias": "Псевдонім", - "alias-required": "Необхідно вказати псевдонім перегляду сутності.", - "remove-alias": "Видалити псевдонім перегляду сутності", - "add-alias": "Додати псевдонім перегляду сутності", - "name-starts-with": "Ім'я перегляду сутності починається з", - "entity-view-list": "Список перегляду сутності", + "alias-required": "Необхідно вказати псевдонім представлення сутності.", + "remove-alias": "Видалити псевдонім представлення сутності", + "add-alias": "Додати псевдонім представлення сутності", + "name-starts-with": "Ім'я представлення сутності починається з", + "entity-view-list": "Список представленнь сутності", "use-entity-view-name-filter": "Використати фільтр", - "entity-view-list-empty": "Не вибрано жодного перегляду сутності.", + "entity-view-list-empty": "Не вибрано жодного представлення сутності.", "entity-view-name-filter-required": "Необхідно вказвти фільтр назв представлення сутності.", - "entity-view-name-filter-no-entity-view-matched": "Перегляди сутностей, назви яких починаються з '{{entityView}}' не знайдено.", - "add": "Додати перегляд сутності", + "entity-view-name-filter-no-entity-view-matched": "Представлення сутностей, назви яких починаються з '{{entityView}}' не знайдено.", + "add": "Додати представлення сутності", "assign-to-customer": "Призначити клієнту", - "assign-entity-view-to-customer": "Призначити перегляд(и) сутності(ей) клієнту", - "assign-entity-view-to-customer-text": "Будь ласка, виберіть перегляд сутності для призначення клієнту", - "no-entity-views-text": "Перегляду сутності не знайдено", - "assign-to-customer-text": "Будь ласка, виберіть клієнта, для призначиення перегляду(ів) сутності(ей)", - "entity-view-details": "Деталі перегляду сутності", - "add-entity-view-text": "Додати новий перегляд сутносі", - "delete": "Видалити перегляд сутності", - "assign-entity-views": "Призначити перегляд сутності", - "assign-entity-views-text": "Призначити { count, plural, 1 {1 перегляд сутності} other {# перегляди сутностей } } клієнту", - "delete-entity-views": "Видалити перегляди сутностей", - "unassign-from-customer": "Позбавити клієнта", - "unassign-entity-views": "Позбавити переглядів сутностей", - "unassign-entity-views-action-title": "Позбавити { count, plural, 1 {1 перегляду сутності} other {# переглядів сутностей} } клієнта", - "assign-new-entity-view": "Призначити новий перегляд сутності", - "delete-entity-view-title": "Ви впевнені, що хочете видалити перегляд сутності'{{entityViewName}}'?", - "delete-entity-view-text": "Будьте обережні, після підтвердження, перегляд сутності та всі пов'язані з нею дані стануть недоступними.", - "delete-entity-views-title": "Ви впевнені, що хочете видалити перегляд сутності { count, plural, 1 {1 перегляд сутності } other {# перегляди сутностей } }?", - "delete-entity-views-action-title": "Видалити { count, plural, 1 {1 перегляд сутності } other {# перегляди сутностей } }", - "delete-entity-views-text": "Будьте обережні, після підтвердження, всі виділені перегляди сутностей та дні, пов'язані з ними стануть недоступними.", - "unassign-entity-view-title": "Ви впевнені, що хочете позбавити перегляду сутності '{{entityViewName}}'?", - "unassign-entity-view-text": "Після підтвердження клієнт буде позбавлений перегляду сутності. Дані перегляду сутності не будуть доступні клієнту.", - "unassign-entity-view": "Позбавити перегляду сутності", - "unassign-entity-views-title": "Ви впевнені, що хочете позбавити { count, plural, 1 {1 перегляду сутності} other {# переглядів сутностей} }?", - "unassign-entity-views-text": "Після підтвердження, клієнта буде позбавлено всіх виділених переглядів сутності. Дані переглядів сутностей не будуть доступні клієнту .", - "entity-view-type": "Тип перегляду сутності", - "entity-view-type-required": "Необхідно вказати тип перегляду сутності.", - "select-entity-view-type": "Виберіть тип перегляду сутності", - "enter-entity-view-type": "Введіть тип перегляду сутності", - "any-entity-view": "Будь-який перегляд сутності", - "no-entity-view-types-matching": "Не знайдено жодних типів перегляду сутності, що відповідають '{{entitySubtype}}'.", - "entity-view-type-list-empty": "Не вибрано тип перегляду сутності.", - "entity-view-types": "Типи перегляду сутності", + "assign-entity-view-to-customer": "Призначити представлення сутності(ей) клієнту", + "assign-entity-view-to-customer-text": "Будь ласка, виберіть представлення сутності для призначення клієнту", + "no-entity-views-text": "Представлення сутності не знайдено", + "assign-to-customer-text": "Будь ласка, виберіть клієнта, для призначиення представлення(ь) сутності(ей)", + "entity-view-details": "Деталі представлення сутності", + "add-entity-view-text": "Додати нове представлення сутності", + "delete": "Видалити представлення сутності", + "assign-entity-views": "Призначити представлення сутності", + "assign-entity-views-text": "Призначити { count, plural, 1 {1 представлення сутності} other {# представлення сутностей } } клієнту", + "delete-entity-views": "Видалити представлення сутностей", + "unassign-from-customer": "Відкликати у клієнта", + "unassign-entity-views": "Відкликати представлення сутностей", + "unassign-entity-views-action-title": "Відкликати { count, plural, 1 {1 представлення сутності} other {# представлень сутностей} } у клієнта", + "assign-new-entity-view": "Призначити нове представлення сутності", + "delete-entity-view-title": "Ви впевнені, що хочете видалити представлення сутності'{{entityViewName}}'?", + "delete-entity-view-text": "Будьте обережні, після підтвердження, представлення сутності та всі пов'язані з ним дані стануть недоступними.", + "delete-entity-views-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 представлення сутності } other {# представлення сутностей } }?", + "delete-entity-views-action-title": "Видалити { count, plural, 1 {1 представлення сутності } other {# представлення сутностей } }", + "delete-entity-views-text": "Будьте обережні, після підтвердження, всі виділені представлення сутностей та дні, пов'язані з ними стануть недоступними.", + "unassign-entity-view-title": "Ви впевнені, що хочете відкликати представлення сутності '{{entityViewName}}'?", + "unassign-entity-view-text": "Після підтвердження представлення сутності буде відкликане у клієнта. Дані представлення сутності не будуть доступні клієнту.", + "unassign-entity-view": "Відкликати представлення сутності", + "unassign-entity-views-title": "Ви впевнені, що хочете відкликати { count, plural, 1 {1 представлення сутності} other {# представлень сутностей} }?", + "unassign-entity-views-text": "Після підтвердження, клієнта буде позбавлено всіх виділених представлень сутностей. Дані представлень сутностей не будуть доступні клієнту.", + "entity-view-type": "Тип представлення сутності", + "entity-view-type-required": "Необхідно вказати тип представлення сутності.", + "select-entity-view-type": "Виберіть тип представлення сутності", + "enter-entity-view-type": "Введіть тип представлення сутності", + "any-entity-view": "Будь-яке представлення сутності", + "no-entity-view-types-matching": "Не знайдено жодних типів представлення сутності, що відповідають '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Не вибрано тип представлення сутності.", + "entity-view-types": "Типи представлення сутності", "name": "Ім'я", "name-required": "Необхідно вказати ім'я.", "description": "Опис", "events": "Події", "details": "Деталі", - "copyId": "Скопіювати Id перегляду сутності", + "copyId": "Скопіювати Id представлення сутності", "assignedToCustomer": "Призначений клієнту", - "unable-entity-view-device-alias-title": "Неможливо видалити псевдонім перегляду сутності", + "unable-entity-view-device-alias-title": "Неможливо видалити псевдонім представлення сутності", "unable-entity-view-device-alias-text": "Не вдалося видалити псевдонім пристрою'{{entityViewAlias}}', так як він використовується наступним(и) віджетом(ами):
{{widgetsList}}", - "select-entity-view": "Виберати перегляд сутності", - "make-public": "Зробити перегляд сутності публічним", + "select-entity-view": "Вибрати представлення сутності", + "make-public": "Зробити представлення сутності публічним", + "make-private": "Зробити представлення сутності приватним", "start-date": "Дата початку", "start-ts": "Час початку", "end-date": "Дата закінчення", @@ -1109,9 +1117,13 @@ "timeseries-placeholder": "Телеметрія", "target-entity": "Цільова сутність", "attributes-propagation": "Поширення атрибутів", - "attributes-propagation-hint": "Перегляд сутностей автоматично копіюватиме вказані атрибути з цільової сутності кожного разу, коли ви зберігаєте або оновлюєте ці перегляди. В цілях продуктивності, атрибути цільової сутності не поширюються на представлення сутності на кожній зміні їх атрибутів. Можна ввімкнути автоматичне поширення, налаштувавши у вашому ланцюжку правило \"copy to view\" і пов'язуючи його з повідомленнями типу \"Post attributes\" і \"Attributes Updated\"..", + "attributes-propagation-hint": "Представлення сутності автоматично копіюватиме вказані атрибути з цільової сутності кожного разу, коли ви зберігаєте або оновлюєте його. В цілях продуктивності, атрибути цільової сутності не поширюються на представлення сутності при кожній зміні її атрибутів. Можна ввімкнути автоматичне поширення, налаштувавши правило \"copy to view\" у вашому ланцюжку правил і пов'язуючи його з повідомленнями типу \"Post attributes\" і \"Attributes Updated\"..", "timeseries-data": "Дані телеметрії", - "timeseries-data-hint": "Налаштуйте ключі даних телеметрії цільової сутності, які будуть доступні перегляду сутності. Ці дані доступні лише для читання." + "timeseries-data-hint": "Налаштуйте ключі даних телеметрії цільової сутності, які будуть доступні представленню сутності. Ці дані доступні лише для читання.", + "make-public-entity-view-title": "Ви впевнені, що бажаєте зробити представлення сутності '{{entityViewName}}' публічним?", + "make-public-entity-view-text": "Після підтвердження представлення сутності і всі пов'язані з ним дані стануть публічними і будуть доступні для інших користувачів.", + "make-private-entity-view-title": "Ви впевнені, що бажаєте зробити представлення сутності '{{entityViewName}}' приватним?", + "make-private-entity-view-text": "Після підтвердження представлення сутності і всі пов'язані з ним дані стануть приватними і не будуть доступні для інших користувачів." }, "event": { "events": "Події", @@ -1264,6 +1276,7 @@ "modbus-add-server": "Додати сервер/ведений пристрій", "modbus-add-server-prompt": "Будь ласка, додайте сервер/ведений пристрій", "modbus-transport": "Транспорт", + "modbus-tcp-reconnect": "Перепідключатися автоматично", "modbus-port-name": "Ім'я послідовного порту", "modbus-encoding": "Кодування", "modbus-parity": "Паритет", @@ -1321,7 +1334,8 @@ "add-item-text": "Додати новий елемент", "no-items-text": "Не знайдено жодного елемента", "item-details": "Деталі елемента", - "delete-item": "Видалити елементи", + "delete-item": "Видалити елемент", + "delete-items": "Видалити елементи", "scroll-to-top": "Перейти угору" }, "help": { @@ -1338,7 +1352,39 @@ "import": { "no-file": "Не вибрано жодного файлу", "drop-file": "Перетягніть JSON файл, або клацніть, щоб вибрати файл для завантаження.", - "drop-csv-file": "Перетягніть CSV файл, або клацніть, щоб вибрати файл для завантаження." + "drop-csv-file": "Перетягніть CSV файл, або клацніть, щоб вибрати файл для завантаження.", + "column-value": "Значення", + "column-title": "Назва", + "column-example": "Приклад значень даних", + "column-key": "Ключ атрибута/телеметрії", + "csv-delimiter": "Розділювач в CSV файлі", + "csv-first-line-header": "Перший рядок містить назви колонок", + "csv-update-data": "Оновити атрибути/телеметрію", + "import-csv-number-columns-error": "Файл має містити як мінімум дві колонки", + "import-csv-invalid-format-error": "Невірний формат даних. Рядок: '{{line}}'", + "column-type": { + "name": "Назва", + "type": "Тип", + "column-type": "Тип колонки", + "client-attribute": "Атрибут клієнта", + "shared-attribute": "Спільний атрибут", + "server-attribute": "Атрибут сервера", + "timeseries": "Телеметрія", + "entity-field": "Entity field", + "access-token": "Токен" + }, + "stepper-text": { + "select-file": "Виберіть файл", + "configuration": "Конфігурація імпорту", + "column-type": "Виберіть тип колонок", + "creat-entities": "Створення нових сутностей", + "done": "Завершено" + }, + "message": { + "create-entities": "{{count}} нову(их) сутність(ей) успішно створено.", + "update-entities": "{{count}} сутність(ей) успішно оновлено.", + "error-entities": "Виникла помилка при створенні {{count}} сутності(ей)." + } }, "integration": { "integration": "Інтеграція", @@ -1681,7 +1727,7 @@ "no-link-labels-found": "Не знайдено жодних міток посилання", "no-link-label-matching": "Мітка'{{label}}' не знайдена.", "create-new-link-label": "Створити нову!", - "type-filter": "Filter", + "type-filter": "Фільтр", "type-filter-details": "Фільтрувати вхідні повідомлення з заданими умовами", "type-enrichment": "Насичення", "type-enrichment-details": "Додати додаткову інформацію до метаданих повідомлень", @@ -1690,7 +1736,7 @@ "type-action": "Дія", "type-action-details": "Виконати задану дію", "type-analytics": "Аналітика", - "type-analytics-details": "Виконати аналіз потокових або збережених даних", + "type-analytics-details": "Виконує аналіз потокових або збережених даних", "type-external": "Зовнішній", "type-external-details": "Взаємодіє з зовнішньою системою", "type-rule-chain": "Ланцюг правил", @@ -1711,12 +1757,13 @@ "metadata-required": "Записи метаданих не можуть бути порожніми.", "output": "Вихід", "test": "Тест", - "help": "Допомога" + "help": "Допомога", + "reset-debug-mode": "Вимкнути режим налогодження у всіх правилах" }, "scheduler": { "scheduler": "Планувальник", - "scheduler-event": "Scheduler event Планування події. Запланувати подію. Подія планувальника", - "select-scheduler-event": "Виберати подію", + "scheduler-event": "Подія планувальника", + "select-scheduler-event": "Вибрати подію", "no-scheduler-events-matching": "Не знайдено жодних подій, які відповідають '{{entity}}'.", "scheduler-event-required": "Необхвдно вказати заплановану подію", "management": "Управління планувальником", @@ -1726,7 +1773,7 @@ "created-time": "Час створення", "name": "Ім'я", "type": "Тип", - "assigned_customer": "Призначений клієнт", + "created_customer": "Створено клієнтом", "edit-scheduler-event": "Редагувати подію", "delete-scheduler-event": "Видалити подію", "no-scheduler-events": "Не знайдено жодних запланованих подій", @@ -1748,8 +1795,9 @@ "daily": "Щодня", "weekly": "Щотижня", "repeats-required": "Потрібно вказати повторення.", - "repeat-on": "Повторити на", - "ends-on": "Завершити на", + "repeat-on": "Повторювати по", + "repeat-every": "Повторювати кожний(у)", + "ends-on": "Завершення", "sunday-label": "Нд", "monday-label": "Пн", "tuesday-label": "Вт", @@ -1784,7 +1832,7 @@ "navigate-next": "Перейти далі", "starting-from": "Починаючи з", "until": "до", - "on": "на", + "on": "в", "sunday": "Неділя", "monday": "Понеділок", "tuesday": "Вівторок", @@ -1792,17 +1840,22 @@ "thursday": "Четвер", "friday": "П'ятниця", "saturday": "Субота", - "originator": "Засновник", - "single-entity": "Самостійна сутність", + "originator": "Ініціатор", + "single-entity": "Єдина сутність", "group-of-entities": "Група сутностей", "single-device": "Один пристрій", "group-of-devices": "Група пристроїв", - "message-body": "Тіло повідомлення", + "message-body": "Текст повідомлення", "target": "Ціль", "rpc-method": "Метод", "rpc-method-required": "Необхідно вказати метод", - "rpc-params": "Парами. Парні.", - "select-dashboard-state": "Виберіть стан панелі візуалізації" + "rpc-params": "Параметри", + "select-dashboard-state": "Виберіть стан панелі візуалізації", + "hours": "Години", + "minutes": "Хвилини", + "seconds": "Секунди", + "time-interval-required": "Необхідно вказати часовий інтервал", + "time-unit-required": "Необхідно вказати одиниці часу" }, "report": { "report-config": "Конфігурація звіту", @@ -1828,8 +1881,8 @@ "bcc": "Bcc", "subject": "Тема", "subject-required": "Необхідно вказати тему.", - "body": "Тіло", - "body-required": "Необідно вказати тіло." + "body": "Текст", + "body-required": "Лист не може бути пустим." }, "blob-entity": { "blob-entity": "Blob сутності", @@ -1841,10 +1894,10 @@ "clear-search": "Очистити пошук", "no-blob-entities-prompt": "Файлів не знайдено", "report": "Звіт", - "created-time": "Створено час", + "created-time": "Час створення", "name": "Ім'я", "type": "Тип", - "assigned_customer": "Призначений клієнт", + "created_customer": "Створено клієнтом", "download-blob-entity": "Завантажити файл", "delete-blob-entity": "Видалити файл", "delete-blob-entity-title": "Ви впевнені, що хочете видалити файл '{{blobEntityName}}'?", @@ -1852,7 +1905,7 @@ }, "timezone": { "timezone": "Часовий пояс", - "select-timezone": "Виберати часовий пояс ", + "select-timezone": "Вибрати часовий пояс ", "no-timezones-matching": "Не знайдено жодних часових поясів, які відповідають '{{timezone}}'.", "timezone-required": "Необхідно вказати часовий пояс." }, @@ -1877,7 +1930,7 @@ "description": "Опис", "details": "Деталі", "events": "Події", - "copyId": "Крпіювати Id власника", + "copyId": "Копіювати Id власника", "idCopiedMessage": "Id власника скопійовано в буфер обміну", "select-tenant": "Вибрати власника", "no-tenants-matching": "Не знайдено жодних власників, які відповідають '{{entity}}'.", @@ -1908,7 +1961,8 @@ "edit": "Редагувати вікно часу", "date-range": "Проміжок часу", "last": "Останнє", - "time-period": "Період часу" + "time-period": "Період часу", + "hide": "Приховати" }, "user": { "user": "Користувач", @@ -2088,6 +2142,7 @@ "decimals": "Кількість цифр після коми", "timewindow": "Вікно часу", "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації", + "display-timewindow": "Показувати вікно часу", "display-legend": "Показати легенду", "datasources": "Джерела даних", "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }", @@ -2112,7 +2167,10 @@ "edit-action": "Редагувати дію", "delete-action": "Видалити дію", "delete-action-title": "Видалити дію віджета", - "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?" + "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?", + "display-icon": "Показувати іконку у назві", + "icon-color": "Колір іконки", + "icon-size": "Розмір іконки" }, "widget-type": { "import": "Імпортувати тип віджета", @@ -2240,17 +2298,18 @@ "domain-name": "Доменне ім'я" }, "icon": { - "icon": "веб-іконка", - "select-icon": "Виберіть веб-іконку", - "material-icons": "Матеріал веб-іконки", - "show-all": "Показати всі веб-іконки" + "icon": "Іконка", + "select-icon": "Виберіть Іконку", + "material-icons": "Іконки в стилі Material", + "show-all": "Показати всі іконки" }, "custom": { "widget-action": { - "action-cell-button": "Кнопка дії клітинки", + "action-cell-button": "Кнопка дії в комірці таблиці", "row-click": "Клацніть на рядок", "marker-click": "Клацніть на маркер", - "tooltip-tag-action": "Дії при підказці" + "polygon-click": "Дія при натисканні на полігон", + "tooltip-tag-action": "Дія при натисканні на посилання в підказці" } }, "language": { From c85f218993f7e499280d2d74438917c84ffea6b4 Mon Sep 17 00:00:00 2001 From: Valerii Sosliuk Date: Thu, 17 Oct 2019 12:49:02 +0300 Subject: [PATCH 017/120] Alarm Clear Test Fixed --- .../java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 32318edcb8..ea800f48bc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -270,6 +270,7 @@ public class TbAlarmNodeTest { // when(detailsJs.executeJson(msg)).thenReturn(null); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm)); when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true)); + when(alarmService.findAlarmByIdAsync(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()))).thenReturn(Futures.immediateFuture(activeAlarm)); // doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm); node.onMsg(ctx, msg); From 467d7b5c3c5d9dce13b82e3c2da4dc15fcc0999f Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 17 Oct 2019 15:25:24 +0300 Subject: [PATCH 018/120] Flots comparison option (#2079) * Add new hidden widget cards for latest and timeseries value * Revert "Add new hidden widget cards for latest and timeseries value" This reverts commit 09b73d5afcc66baf942a776f83a0295700f83d22. * Allow hiding of zero/false dataKey values from tbFlot widgets tooltips * comparison option draft * Added dataKey setting for excluding from Stacking mode in chart widgets. * Flot comparison option (draft) * Flot comparison option (draft) * Fix color generation for additional keys * Add new time options for comparison * Add ability to define points symbol and line width in 'line' flot charts * Change history timeinterval calculation, translations definition --- .../json/system/widget_bundles/charts.json | 6 +- ui/src/app/api/subscription.js | 100 +++- ui/src/app/api/time.service.js | 23 + ui/src/app/common/utils.service.js | 22 +- .../components/widget/widget.controller.js | 6 +- ui/src/app/locale/locale.constant-en_US.json | 8 +- ui/src/app/locale/locale.constant-ru_RU.json | 8 +- ui/src/app/locale/locale.constant-uk_UA.json | 8 +- ui/src/app/widget/lib/flot-widget.js | 539 +++++++++++++++--- 9 files changed, 617 insertions(+), 103 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 2ecd90be28..9c97a03084 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -35,7 +35,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" @@ -147,7 +147,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" @@ -163,7 +163,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index e89650c6a4..728af2d38f 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -132,6 +132,13 @@ export default class Subscription { } this.subscriptionTimewindow = null; + this.comparisonEnabled = options.comparisonEnabled; + if (this.comparisonEnabled) { + this.timeForComparison = options.timeForComparison; + + this.comparisonTimeWindow = {}; + this.timewindowForComparison = null; + } this.units = options.units || ''; this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2; @@ -311,13 +318,28 @@ export default class Subscription { } configureData() { + var additionalDatasources = []; var dataIndex = 0; + var additionalKeysNumber = 0; for (var i = 0; i < this.datasources.length; i++) { var datasource = this.datasources[i]; + var additionalDataKeys = []; + let datasourceAdditionalKeysNumber = 0; + for (var a = 0; a < datasource.dataKeys.length; a++) { var dataKey = datasource.dataKeys[a]; dataKey.hidden = false; dataKey.pattern = angular.copy(dataKey.label); + + if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) { + datasourceAdditionalKeysNumber++; + additionalKeysNumber++; + let additionalDataKey = this.ctx.utils.createAdditionalDataKey(dataKey,datasource, this.timeForComparison,this.datasources,additionalKeysNumber); + dataKey.settings.comparisonSettings.color = additionalDataKey.color; + + additionalDataKeys.push(additionalDataKey); + } + var datasourceData = { datasource: datasource, dataKey: dataKey, @@ -341,8 +363,46 @@ export default class Subscription { this.legendData.data.push(legendKeyData); } } + + if (datasourceAdditionalKeysNumber > 0) { + let additionalDatasource = angular.copy(datasource); + additionalDatasource.dataKeys = additionalDataKeys; + additionalDatasource.isAdditional = true; + additionalDatasources.push(additionalDatasource); + } + } + + for (var j=0; j < additionalDatasources.length; j++) { + let additionalDatasource = additionalDatasources[j]; + for (var k=0; k < additionalDatasource.dataKeys.length; k++) { + let additionalDataKey = additionalDatasource.dataKeys[k]; + var additionalDatasourceData = { + datasource: additionalDatasource, + dataKey: additionalDataKey, + data: [] + }; + this.data.push(additionalDatasourceData); + this.hiddenData.push({data: []}); + if (this.displayLegend) { + var additionalLegendKey = { + dataKey: additionalDataKey, + dataIndex: dataIndex++ + }; + this.legendData.keys.push(additionalLegendKey); + var additionalLegendKeyData = { + min: null, + max: null, + avg: null, + total: null, + hidden: false + }; + this.legendData.data.push(additionalLegendKeyData); + } + } } + this.datasources = this.datasources.concat(additionalDatasources); + var subscription = this; var registration; @@ -638,7 +698,7 @@ export default class Subscription { updateTimewindow() { this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; if (this.subscriptionTimewindow.realtimeWindowMs) { - this.timeWindow.maxTime = (new Date).getTime() + this.timeWindow.stDiff; + this.timeWindow.maxTime = (moment()).valueOf() + this.timeWindow.stDiff;//eslint-disable-line this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; } else if (this.subscriptionTimewindow.fixedWindow) { this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; @@ -659,6 +719,26 @@ export default class Subscription { return this.subscriptionTimewindow; } + updateComparisonTimewindow() { + this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; + if (this.timewindowForComparison.realtimeWindowMs) { + this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); //eslint-disable-line + this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs; + } else if (this.timewindowForComparison.fixedWindow) { + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; + } + } + + updateSubscriptionForComparison() { + if (!this.subscriptionTimewindow) { + this.subscriptionTimewindow = this.updateRealtimeSubscription(); + } + this.timewindowForComparison = this.ctx.timeService.createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); + this.updateComparisonTimewindow(); + return this.timewindowForComparison; + } + dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { for (var x = 0; x < this.datasourceListeners.length; x++) { this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; @@ -689,6 +769,9 @@ export default class Subscription { if (update) { if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { this.updateTimewindow(); + if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) { + this.updateComparisonTimewindow(); + } } currentData.data = sourceData.data; if (this.caulculateLegendData) { @@ -745,6 +828,9 @@ export default class Subscription { this.notifyDataLoading(); if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { this.updateRealtimeSubscription(); + if (this.comparisonEnabled) { + this.updateSubscriptionForComparison(); + } if (this.subscriptionTimewindow.fixedWindow) { this.onDataUpdated(); } @@ -776,6 +862,17 @@ export default class Subscription { datasourceIndex: index }; + if (this.comparisonEnabled && datasource.isAdditional) { + listener.subscriptionTimewindow = this.timewindowForComparison; + listener.updateRealtimeSubscription = function () { + this.subscriptionTimewindow = subscription.updateSubscriptionForComparison(); + return this.subscriptionTimewindow; + }; + listener.setRealtimeSubscription = function () { + subscription.updateSubscriptionForComparison(); + }; + } + for (var a = 0; a < datasource.dataKeys.length; a++) { this.data[index + a].data = []; } @@ -908,7 +1005,6 @@ export default class Subscription { }); this.registrations = []; } - } function calculateMin(data) { diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js index 878d43eed8..6b2e3f7687 100644 --- a/ui/src/app/api/time.service.js +++ b/ui/src/app/api/time.service.js @@ -47,6 +47,7 @@ function TimeService($translate, $http, $q, types) { defaultTimewindow: defaultTimewindow, toHistoryTimewindow: toHistoryTimewindow, createSubscriptionTimewindow: createSubscriptionTimewindow, + createTimewindowForComparison: createTimewindowForComparison, getMaxDatapointsLimit: function () { return maxDatapointsLimit; }, @@ -383,5 +384,27 @@ function TimeService($translate, $http, $q, types) { } } + function createTimewindowForComparison(subscriptionTimewindow, timeUnit) { + var timewindowForComparison = { + fixedWindow: null, + realtimeWindowMs: null, + aggregation: subscriptionTimewindow.aggregation + }; + + if (subscriptionTimewindow.realtimeWindowMs) { + timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf(); //eslint-disable-line + timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs; + } else if (subscriptionTimewindow.fixedWindow) { + var timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + var endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); //eslint-disable-line + timewindowForComparison.startTs = endTimeMs - timeInterval; + timewindowForComparison.fixedWindow = { + startTimeMs: timewindowForComparison.startTs, + endTimeMs: endTimeMs + }; + } + + return timewindowForComparison; + } } diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index dd2a32de3e..23a29308de 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -144,6 +144,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t isLocalUrl: isLocalUrl, validateDatasources: validateDatasources, createKey: createKey, + createAdditionalDataKey: createAdditionalDataKey, createLabelFromDatasource: createLabelFromDatasource, insertVariable: insertVariable, customTranslation: customTranslation, @@ -411,8 +412,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t return copy; } - function genNextColor(datasources) { - var index = 0; + function genNextColor(datasources, initialIndex) { + var index = initialIndex || 0; if (datasources) { for (var i = 0; i < datasources.length; i++) { var datasource = datasources[i]; @@ -491,6 +492,23 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t return dataKey; } + function createAdditionalDataKey(dataKey, datasource, timeUnit, datasources, additionalKeysNumber) { + let additionalDataKey = angular.copy(dataKey); + if (dataKey.settings.comparisonSettings.comparisonValuesLabel) { + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); + } else { + additionalDataKey.label = dataKey.label + ' ' + $translate.instant('legend.comparison-time-ago.'+timeUnit); + } + additionalDataKey.pattern = additionalDataKey.label; + if (dataKey.settings.comparisonSettings.color) { + additionalDataKey.color = dataKey.settings.comparisonSettings.color; + } else { + additionalDataKey.color = genNextColor(datasources, additionalKeysNumber); + } + additionalDataKey._hash = Math.random(); + return additionalDataKey; + } + function createLabelFromDatasource(datasource, pattern) { var label = angular.copy(pattern); var match = varsRegex.exec(pattern); diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index 593c3de36e..5d98e44d0e 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -357,8 +357,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { options = { type: widget.type, - stateData: vm.typeParameters.stateData - } + stateData: vm.typeParameters.stateData, + comparisonEnabled: widgetContext.settings.comparisonEnabled, + timeForComparison: widgetContext.settings.timeForComparison + }; if (widget.type == types.widgetType.alarm.value) { options.alarmSource = angular.copy(widget.config.alarmSource); options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ? diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 63050c0fa1..b7f6a0f23f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1195,7 +1195,13 @@ "min": "min", "max": "max", "avg": "avg", - "total": "total" + "total": "total", + "comparison-time-ago": { + "days": "(day ago)", + "weeks": "(week ago)", + "months": "(month ago)", + "years": "(year ago)" + } }, "login": { "login": "Login", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 42fd47e2b8..c2a9fd2109 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1169,7 +1169,13 @@ "min": "Мин", "max": "Макс", "avg": "Среднее", - "total": "Сумма" + "total": "Сумма", + "comparison-time-ago": { + "days": "(день назад)", + "weeks": "(неделю назад)", + "months": "(месяц назад)", + "years": "(год назад)" + } }, "login": { "login": "Войти", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index fa458eddb3..41c8db576f 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -1584,7 +1584,13 @@ "min": "мін", "max": "макс", "avg": "середнє", - "total": "Сума" + "total": "Сума", + "comparison-time-ago": { + "days": "(день тому)", + "weeks": "(тиждень тому)", + "months": "(місяць тому)", + "years": "(рік тому)" + } }, "login": { "login": "Вхід", diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index 6a26e6bec2..08496d574b 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -23,6 +23,7 @@ import 'flot/src/plugins/jquery.flot.selection'; import 'flot/src/plugins/jquery.flot.pie'; import 'flot/src/plugins/jquery.flot.crosshair'; import 'flot/src/plugins/jquery.flot.stack'; +import 'flot/src/plugins/jquery.flot.symbol'; import 'flot.curvedlines/curvedLines'; /* eslint-disable angular/angularelement */ @@ -32,6 +33,7 @@ export default class TbFlot { this.ctx = ctx; this.chartType = chartType || 'line'; var settings = ctx.settings; + var utils = this.ctx.$scope.$injector.get('utils'); ctx.tooltip = $('#flot-series-tooltip'); if (ctx.tooltip.length === 0) { @@ -59,7 +61,7 @@ export default class TbFlot { divElement.css({ display: "flex", alignItems: "center", - justifyContent: "flex-start" + justifyContent: "space-between" }); var lineSpan = $(''); lineSpan.css({ @@ -125,55 +127,99 @@ export default class TbFlot { } else { ctx.tooltipFormatter = function(hoverInfo, seriesIndex) { var content = ''; - var timestamp = parseInt(hoverInfo.time); - var date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); - var dateDiv = $('
' + date + '
'); - dateDiv.css({ - display: "flex", - alignItems: "center", - justifyContent: "center", - padding: "4px", - fontWeight: "700" - }); - content += dateDiv.prop('outerHTML'); + if (tbFlot.ctx.tooltipIndividual) { - var found = hoverInfo.seriesHover.filter((seriesHover) => { + var seriesHoverArray; + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + seriesHoverArray = hoverInfo[0].seriesHover.concat(hoverInfo[1].seriesHover); + } else { + seriesHoverArray = hoverInfo[0].seriesHover; + } + var found = seriesHoverArray.filter((seriesHover) => { return seriesHover.index === seriesIndex; }); if (found && found.length) { + let timestamp; + if (!angular.isNumber(hoverInfo[0].time) || (found[0].time < hoverInfo[0].time)) { + timestamp = parseInt(hoverInfo[1].time); + } else { + timestamp = parseInt(hoverInfo[0].time); + } + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); + let dateDiv = $('
' + date + '
'); + dateDiv.css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "4px", + fontWeight: "700" + }); + content += dateDiv.prop('outerHTML'); content += seriesInfoDivFromInfo(found[0], seriesIndex); } } else { - var seriesDiv = $('
'); - seriesDiv.css({ - display: "flex", - flexDirection: "row" - }); - const maxRows = 15; - var columns = Math.ceil(hoverInfo.seriesHover.length / maxRows); - var columnsContent = ''; - for (var c = 0; c < columns; c++) { - var columnDiv = $('
'); - columnDiv.css({ - display: "flex", - flexDirection: "column" - }); - var columnContent = ''; - for (var i = c*maxRows; i < (c+1)*maxRows; i++) { - if (i == hoverInfo.seriesHover.length) { - break; + var maxRows; + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + maxRows = 5; + } else { + maxRows = 15; + } + var columns = 0; + if (hoverInfo[1] && (hoverInfo[1].seriesHover.length > hoverInfo[0].seriesHover.length)) { + columns = Math.ceil(hoverInfo[1].seriesHover.length / maxRows); + } else { + columns = Math.ceil(hoverInfo[0].seriesHover.length / maxRows); + } + + for (var j = 0; j < hoverInfo.length; j++) { + var hoverData = hoverInfo[j]; + if (angular.isNumber(hoverData.time)) { + var columnsContent = ''; + let timestamp = parseInt(hoverData.time); + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); + let dateDiv = $('
' + date + '
'); + dateDiv.css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "4px", + fontWeight: "700" + }); + content += dateDiv.prop('outerHTML'); + + var seriesDiv = $('
'); + seriesDiv.css({ + display: "flex", + flexDirection: "row" + }); + for (var c = 0; c < columns; c++) { + var columnDiv = $('
'); + columnDiv.css({ + display: "flex", + flexDirection: "column", + flex: "1" + }); + var columnContent = ''; + for (var i = c*maxRows; i < (c+1)*maxRows; i++) { + if (i >= hoverData.seriesHover.length) { + break; + } + var seriesHoverInfo = hoverData.seriesHover[i]; + columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); + } + columnDiv.html(columnContent); + + if (columnContent) { + if (c > 0) { + columnsContent += ''; + } + columnsContent += columnDiv.prop('outerHTML'); + } } - var seriesHoverInfo = hoverInfo.seriesHover[i]; - columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); - } - columnDiv.html(columnContent); - if (c > 0) { - columnsContent += ''; + seriesDiv.html(columnsContent); + content += seriesDiv.prop('outerHTML'); } - columnsContent += columnDiv.prop('outerHTML'); } - seriesDiv.html(columnsContent); - content += seriesDiv.prop('outerHTML'); } return content; }; @@ -185,6 +231,7 @@ export default class TbFlot { ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false); ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false; + ctx.hideZeros = angular.isDefined(settings.hideZeros) ? settings.hideZeros : false; var font = { color: settings.fontColor || "#545454", @@ -209,7 +256,8 @@ export default class TbFlot { }; if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { - options.xaxis = { + options.xaxes = []; + this.xaxis = { mode: 'time', timezone: 'browser', font: angular.copy(font), @@ -220,16 +268,11 @@ export default class TbFlot { labelFont: angular.copy(font) }; if (settings.xaxis) { - if (settings.xaxis.showLabels === false) { - options.xaxis.tickFormatter = function() { - return ''; - }; - } - options.xaxis.font.color = settings.xaxis.color || options.xaxis.font.color; - options.xaxis.label = settings.xaxis.title || null; - options.xaxis.labelFont.color = options.xaxis.font.color; - options.xaxis.labelFont.size = options.xaxis.font.size+2; - options.xaxis.labelFont.weight = "bold"; + this.xaxis.font.color = settings.xaxis.color || this.xaxis.font.color; + this.xaxis.label = utils.customTranslation(settings.xaxis.title, settings.xaxis.title) || null; + this.xaxis.labelFont.color = this.xaxis.font.color; + this.xaxis.labelFont.size = this.xaxis.font.size+2; + this.xaxis.labelFont.weight = "bold"; } ctx.yAxisTickFormatter = function(value/*, axis*/) { @@ -263,7 +306,7 @@ export default class TbFlot { this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color; this.yaxis.min = angular.isDefined(settings.yaxis.min) ? settings.yaxis.min : null; this.yaxis.max = angular.isDefined(settings.yaxis.max) ? settings.yaxis.max : null; - this.yaxis.label = settings.yaxis.title || null; + this.yaxis.label = utils.customTranslation(settings.yaxis.title, settings.yaxis.title) || null; this.yaxis.labelFont.color = this.yaxis.font.color; this.yaxis.labelFont.size = this.yaxis.font.size+2; this.yaxis.labelFont.weight = "bold"; @@ -296,7 +339,7 @@ export default class TbFlot { options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ? settings.grid.outlineWidth : 1; if (settings.grid.verticalLines === false) { - options.xaxis.tickLength = 0; + this.xaxis.tickLength = 0; } if (settings.grid.horizontalLines === false) { this.yaxis.tickLength = 0; @@ -309,14 +352,42 @@ export default class TbFlot { } } - options.crosshair = { - mode: 'x' + options.xaxes[0] = angular.copy(this.xaxis); + + if (settings.xaxis && settings.xaxis.showLabels === false) { + options.xaxes[0].tickFormatter = function() { + return ''; + }; } - options.series = { - stack: settings.stack === true + if (settings.comparisonEnabled) { + var xaxis = angular.copy(this.xaxis); + xaxis.position = 'top'; + if (settings.xaxisSecond) { + if (settings.xaxisSecond.showLabels === false) { + xaxis.tickFormatter = function() { + return ''; + }; + } + xaxis.label = utils.customTranslation(settings.xaxisSecond.title, settings.xaxisSecond.title) || null; + xaxis.position = settings.xaxisSecond.axisPosition; + } + xaxis.tickLength = 0; + options.xaxes.push(xaxis); + + options.series = { + stack: false + }; + } else { + options.series = { + stack: settings.stack === true + }; } + options.crosshair = { + mode: 'x' + }; + if (this.chartType === 'line' && settings.smoothLines) { options.series.curvedLines = { active: true, @@ -429,6 +500,13 @@ export default class TbFlot { series.lines = { fill: keySettings.fillLines === true }; + + if (this.ctx.settings.stack && !this.ctx.settings.comparisonEnabled) { + series.stack = !keySettings.excludeFromStacking; + } else { + series.stack = false; + } + if (this.chartType === 'line' || this.chartType === 'state') { series.lines.show = keySettings.showLines !== false } else { @@ -445,14 +523,23 @@ export default class TbFlot { }; if (keySettings.showPoints === true) { series.points.show = true; - series.points.lineWidth = 5; + series.points.lineWidth = angular.isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5; series.points.radius = angular.isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3; + series.points.symbol = angular.isDefined(keySettings.showPointShape) ? keySettings.showPointShape : 'circle'; + if (series.points.symbol == 'custom' && keySettings.pointShapeFormatter) { + try { + series.points.symbol = new Function('ctx, x, y, radius, shadow', keySettings.pointShapeFormatter); + } catch (e) { + series.points.symbol = 'circle'; + } + } + } if (this.chartType === 'line' && this.ctx.settings.smoothLines && !series.points.show) { series.curvedLines = { apply: true - } + }; } var lineColor = tinycolor(series.dataKey.color); @@ -460,6 +547,15 @@ export default class TbFlot { series.highlightColor = lineColor.toRgbString(); + if (series.datasource.isAdditional) { + series.xaxisIndex = 1; + series.xaxis = 2; + } else { + series.xaxisIndex = 0; + series.xaxis = 1; + } + + if (this.yaxis) { var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits; var yaxis; @@ -477,7 +573,7 @@ export default class TbFlot { series.yaxisIndex = this.yaxes.indexOf(yaxis); series.yaxis = series.yaxisIndex+1; yaxis.keysInfo[i] = {hidden: false}; - yaxis.hidden = false; + yaxis.show = true; } } @@ -491,8 +587,12 @@ export default class TbFlot { this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; } } - this.options.xaxis.min = this.subscription.timeWindow.minTime; - this.options.xaxis.max = this.subscription.timeWindow.maxTime; + this.options.xaxes[0].min = this.subscription.timeWindow.minTime; + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } } this.checkMouseEvents(); @@ -500,6 +600,7 @@ export default class TbFlot { if (this.ctx.plot) { this.ctx.plot.destroy(); } + if (this.chartType === 'pie' && this.ctx.animatedPie) { this.ctx.pieDataAnimationDuration = 250; this.pieData = angular.copy(this.subscription.data); @@ -608,8 +709,12 @@ export default class TbFlot { } } - this.options.xaxis.min = this.subscription.timeWindow.minTime; - this.options.xaxis.max = this.subscription.timeWindow.maxTime; + this.options.xaxes[0].min = this.subscription.timeWindow.minTime; + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } if (this.chartType === 'bar') { if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { this.options.series.bars.barWidth = this.ctx.defaultBarWidth; @@ -623,6 +728,10 @@ export default class TbFlot { } else { this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.ctx.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.ctx.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } if (this.chartType === 'bar') { if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth; @@ -902,6 +1011,11 @@ export default class TbFlot { "type": "string", "default": "" }; + properties["hideZeros"] = { + "title": "Hide zero/false values from tooltip", + "type": "boolean", + "default": false + }; properties["grid"] = { "title": "Grid settings", @@ -1029,6 +1143,7 @@ export default class TbFlot { "key": "tooltipValueFormatter", "type": "javascript" }); + schema["form"].push("hideZeros"); schema["form"].push({ "key": "grid", "items": [ @@ -1079,6 +1194,21 @@ export default class TbFlot { } ] }); + if (chartType === 'graph' || chartType === 'bar') { + schema.groupInfoes = [{ + "formIndex":0, + "GroupTitle":"Common Settings" + }]; + schema.form = [schema.form]; + angular.merge(schema.schema.properties, chartSettingsSchemaForComparison.schema.properties); + schema.schema.required = schema.schema.required.concat(chartSettingsSchemaForComparison.schema.required); + schema.form.push(chartSettingsSchemaForComparison.form); + schema.groupInfoes.push({ + "formIndex":schema.groupInfoes.length, + "GroupTitle":"Comparison Settings" + }); + } + return schema; } @@ -1086,12 +1216,18 @@ export default class TbFlot { return {} } - static datakeySettingsSchema(defaultShowLines) { - return { + static datakeySettingsSchema(defaultShowLines, chartType) { + + var schema = { "schema": { "type": "object", "title": "DataKeySettings", "properties": { + "excludeFromStacking": { + "title": "Exclude from stacking(available in \"Stacking\" mode)", + "type": "boolean", + "default": false + }, "showLines": { "title": "Show lines", "type": "boolean", @@ -1107,6 +1243,25 @@ export default class TbFlot { "type": "boolean", "default": false }, + "showPointShape": { + "title": "Select point shape:", + "type": "string", + "default": "circle" + }, + "pointShapeFormatter": { + "title": "Point shape format function, f(ctx, x, y, radius, shadow)", + "type": "string", + "default": "var size = radius * Math.sqrt(Math.PI) / 2;\n" + + "ctx.moveTo(x - size, y - size);\n" + + "ctx.lineTo(x + size, y + size);\n" + + "ctx.moveTo(x - size, y + size);\n" + + "ctx.lineTo(x + size, y - size);" + }, + "showPointsLineWidth": { + "title": "Line width of points", + "type": "number", + "default": 5 + }, "showPointsRadius": { "title": "Radius of points", "type": "number", @@ -1161,9 +1316,46 @@ export default class TbFlot { "required": ["showLines", "fillLines", "showPoints"] }, "form": [ + "excludeFromStacking", "showLines", "fillLines", "showPoints", + { + "key": "showPointShape", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "circle", + "label": "Circle" + }, + { + "value": "cross", + "label": "Cross" + }, + { + "value": "diamond", + "label": "Diamond" + }, + { + "value": "square", + "label": "Square" + }, + { + "value": "triangle", + "label": "Triangle" + }, + { + "value": "custom", + "label": "Custom function" + } + ] + }, + { + "key": "pointShapeFormatter", + "type": "javascript" + }, + "showPointsLineWidth", "showPointsRadius", { "key": "tooltipValueFormatter", @@ -1195,7 +1387,47 @@ export default class TbFlot { "type": "javascript" } ] + }; + + var properties = schema["schema"]["properties"]; + if (chartType === 'graph' || chartType === 'bar') { + properties["comparisonSettings"] = { + "title": "Comparison Settings", + "type": "object", + "properties": { + "showValuesForComparison": { + "title": "Show historical values for comparison", + "type": "boolean", + "default": true + }, + "comparisonValuesLabel": { + "title": "Historical values label", + "type": "string", + "default": "" + }, + "color": { + "title": "Color", + "type": "string", + "default": "" + } + }, + "required": ["showValuesForComparison"] + }; + schema["form"].push({ + "key": "comparisonSettings", + "items": [ + "comparisonSettings.showValuesForComparison", + "comparisonSettings.comparisonValuesLabel", + { + "key": "comparisonSettings.color", + "type": "color" + } + ] + }); + } + + return schema; } enableMouseEvents() { @@ -1227,10 +1459,15 @@ export default class TbFlot { tooltipHtml = tbFlot.ctx.tooltipFormatter(item); } else { var hoverInfo = tbFlot.getHoverInfo(tbFlot.ctx.plot.getData(), pos); - if (angular.isNumber(hoverInfo.time)) { - hoverInfo.seriesHover.sort(function (a, b) { + if (angular.isNumber(hoverInfo[0].time) || (hoverInfo[1] && angular.isNumber(hoverInfo[1].time))) { + hoverInfo[0].seriesHover.sort(function (a, b) { return b.value - a.value; }); + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + hoverInfo[1].seriesHover.sort(function (a, b) { + return b.value - a.value; + }); + } tooltipHtml = tbFlot.ctx.tooltipFormatter(hoverInfo, item ? item.seriesIndex : -1); } } @@ -1258,9 +1495,11 @@ export default class TbFlot { }); if (multipleModeTooltip) { - for (var i = 0; i < hoverInfo.seriesHover.length; i++) { - var seriesHoverInfo = hoverInfo.seriesHover[i]; - tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); + for (var j = 0; j < hoverInfo.length; j++) { + for (var i = 0; i < hoverInfo[j].seriesHover.length; i++) { + var seriesHoverInfo = hoverInfo[j].seriesHover[i]; + tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); + } } } } @@ -1399,19 +1638,38 @@ export default class TbFlot { getHoverInfo (seriesList, pos) { - var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime; + var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime, hoverData; var last_value = 0; - var results = { + var results = [{ seriesHover: [] - }; + }]; + if (this.ctx.settings.comparisonEnabled) { + results.push({ + seriesHover: [] + }); + var minDistanceHistorical, minTimeHistorical; + } for (i = 0; i < seriesList.length; i++) { series = seriesList[i]; - hoverIndex = this.findHoverIndexFromData(pos.x, series); + var posx; + if (series.datasource.isAdditional) { + posx = pos.x2; + } else { + posx = pos.x; + } + hoverIndex = this.findHoverIndexFromData(posx, series); if (series.data[hoverIndex] && series.data[hoverIndex][0]) { - hoverDistance = pos.x - series.data[hoverIndex][0]; + hoverDistance = posx - series.data[hoverIndex][0]; pointTime = series.data[hoverIndex][0]; - if (!minDistance + if (series.datasource.isAdditional) { + if (!minDistanceHistorical + || (hoverDistance >= 0 && (hoverDistance < minDistanceHistorical || minDistanceHistorical < 0)) + || (hoverDistance < 0 && hoverDistance > minDistanceHistorical)) { + minDistanceHistorical = hoverDistance; + minTimeHistorical = pointTime; + } + } else if (!minDistance || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) || (hoverDistance < 0 && hoverDistance > minDistance)) { minDistance = hoverDistance; @@ -1429,23 +1687,33 @@ export default class TbFlot { } if (series.stack || (series.curvedLines && series.curvedLines.apply)) { - hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); + hoverIndex = this.findHoverIndexFromDataPoints(posx, series, hoverIndex); + } + if (!this.ctx.hideZeros || value) { + hoverData = { + value: value, + hoverIndex: hoverIndex, + color: series.dataKey.color, + label: series.dataKey.label, + units: series.dataKey.units, + decimals: series.dataKey.decimals, + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, + time: pointTime, + distance: hoverDistance, + index: i + }; + if (series.datasource.isAdditional) { + results[1].seriesHover.push(hoverData); + } else { + results[0].seriesHover.push(hoverData); + } } - results.seriesHover.push({ - value: value, - hoverIndex: hoverIndex, - color: series.dataKey.color, - label: series.dataKey.label, - units: series.dataKey.units, - decimals: series.dataKey.decimals, - tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, - time: pointTime, - distance: hoverDistance, - index: i - }); } } - results.time = minTime; + if (results[1] && results[1].seriesHover.length) { + results[1].time = minTimeHistorical; + } + results[0].time = minTime; return results; } @@ -1524,4 +1792,93 @@ export default class TbFlot { } } +const chartSettingsSchemaForComparison = { + "schema": { + "title": "Comparison Settings", + "type": "object", + "properties": { + "comparisonEnabled": { + "title": "Enable comparison", + "type": "boolean", + "default": false + }, + "timeForComparison": { + "title": "Time to show historical data", + "type": "string", + "default": "months" + }, + "xaxisSecond": { + "title": "Second X axis", + "type": "object", + "properties": { + "axisPosition": { + "title": "Axis position", + "type": "string", + "default": "top" + }, + "showLabels": { + "title": "Show labels", + "type": "boolean", + "default": true + }, + "title": { + "title": "Axis title", + "type": "string", + "default": null + } + } + } + }, + "required": [] + }, + "form": [ + "comparisonEnabled", + { + "key": "timeForComparison", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "days", + "label": "Day ago" + }, + { + "value": "weeks", + "label": "Week ago" + }, + { + "value": "months", + "label": "Month ago (default)" + }, + { + "value": "years", + "label": "Year ago" + } + ] + }, + { + "key": "xaxisSecond", + "items": [ + { + "key": "xaxisSecond.axisPosition", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "top", + "label": "Top (default)" + }, + { + "value": "bottom", + "label": "Bottom" + } + ] + }, + "xaxisSecond.showLabels", + "xaxisSecond.title", + ] + } + ] +}; + /* eslint-enable angular/angularelement */ From 8b0d7c1e64a7e3ac4ff830347739079ca1cb9b47 Mon Sep 17 00:00:00 2001 From: Oleg Kolesnik <31017535+jktu2870@users.noreply.github.com> Date: Thu, 17 Oct 2019 15:29:04 +0300 Subject: [PATCH 019/120] UI:fix. Dashboard (widgets order mobile view) (#2029) --- ui/src/app/components/dashboard.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index d7e8b6247c..85ce18eb02 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -353,6 +353,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ ids.sort(function (id1, id2) { return id1.localeCompare(id2); }); + sortWidgets(); if (angular.equals(ids, vm.widgetIds)) { return; } @@ -388,7 +389,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ delete vm.widgetLayoutInfo[widgetId]; } } - sortWidgets(); $mdUtil.nextTick(function () { if (autofillHeight()) { updateMobileOpts(); From 9f266cd239155fc9cb6b70a789d056752eaf9b72 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 17 Oct 2019 15:30:13 +0300 Subject: [PATCH 020/120] Move action button to header widget (#2100) --- .../system/widget_bundles/alarm_widgets.json | 6 ++--- .../json/system/widget_bundles/cards.json | 4 ++-- .../widget_bundles/entity_admin_widgets.json | 10 ++++---- ui/src/app/widget/lib/alarms-table-widget.js | 22 +++++++++++++++++- .../widget/lib/alarms-table-widget.tpl.html | 23 +------------------ .../app/widget/lib/entities-table-widget.js | 12 +++++++++- .../widget/lib/entities-table-widget.tpl.html | 13 +---------- 7 files changed, 44 insertions(+), 46 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json index d811393c4b..2daf3e27c8 100644 --- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json @@ -16,10 +16,10 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStatusFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}" } } ] -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index dab5e7976e..db004ff87f 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -32,9 +32,9 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"enableSelectColumnDisplay\":true},\"title\":\"Entity table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}" } }, { diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json index d9490e0c3b..c0d0fa7cdf 100644 --- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json @@ -16,9 +16,9 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\"},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Add device

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Device name is required.
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddDeviceDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addDeviceForm.$setPristine();\\n let device = {\\n name: vm.deviceName,\\n type: vm.deviceType,\\n label: vm.deviceLabel\\n };\\n deviceService.saveDevice(device).then(\\n (device) => {\\n saveAttributes(device.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Edit device

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Device name is required.
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditDeviceDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n deviceService.getDevice(entityId.id).then(\\n (device) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.device = device;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editDeviceForm.$setPristine();\\n deviceService.saveDevice(vm.device).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteDevice();\\n })\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"device\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Add device

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Device name is required.
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddDeviceDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addDeviceForm.$setPristine();\\n let device = {\\n name: vm.deviceName,\\n type: vm.deviceType,\\n label: vm.deviceLabel\\n };\\n deviceService.saveDevice(device).then(\\n (device) => {\\n saveAttributes(device.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Edit device

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Device name is required.
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditDeviceDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n deviceService.getDevice(entityId.id).then(\\n (device) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.device = device;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editDeviceForm.$setPristine();\\n deviceService.saveDevice(vm.device).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteDevice();\\n })\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}" } }, { @@ -32,10 +32,10 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\"},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Add asset

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Asset name is required.
\\n
\\n
\\n \\n \\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddAssetDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addAssetForm.$setPristine();\\n let asset = {\\n name: vm.assetName,\\n type: vm.assetType\\n };\\n assetService.saveAsset(asset).then(\\n (asset) => {\\n saveAttributes(asset.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Edit asset

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Asset name is required.
\\n
\\n
\\n \\n \\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditAssetDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n assetService.getAsset(entityId.id).then(\\n (asset) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.asset = asset;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editAssetForm.$setPristine();\\n assetService.saveAsset(vm.asset).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteAsset();\\n })\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"asset\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Add asset

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Asset name is required.
\\n
\\n
\\n \\n \\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddAssetDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addAssetForm.$setPristine();\\n let asset = {\\n name: vm.assetName,\\n type: vm.assetType\\n };\\n assetService.saveAsset(asset).then(\\n (asset) => {\\n saveAttributes(asset.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"\\n
\\n \\n
\\n

Edit asset

\\n \\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
Asset name is required.
\\n
\\n
\\n \\n \\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n \\n Create\\n Cancel\\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditAssetDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n assetService.getAsset(entityId.id).then(\\n (asset) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.asset = asset;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editAssetForm.$setPristine();\\n assetService.saveAsset(vm.asset).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteAsset();\\n })\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}" } } ] -} \ No newline at end of file +} diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js index 9fb41851ad..174e72eab3 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.js +++ b/ui/src/app/widget/lib/alarms-table-widget.js @@ -94,6 +94,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia icon: 'search' }; + let columnDisplayAction = { + name: 'entity.columns-to-display', + show: true, + onAction: function($event) { + vm.editColumnsToDisplay($event); + }, + icon: 'view_column' + }; + + let statusFilterAction = { + name: 'alarm.alarm-status-filter', + show: true, + onAction: function($event) { + vm.editAlarmStatusFilter($event); + }, + icon: 'filter_list' + }; + vm.enterFilterMode = enterFilterMode; vm.exitFilterMode = exitFilterMode; vm.onReorder = onReorder; @@ -167,7 +185,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia function initializeConfig() { - vm.ctx.widgetActions = [ vm.searchAction ]; + vm.ctx.widgetActions = [ vm.searchAction, statusFilterAction, columnDisplayAction ]; vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; @@ -215,6 +233,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true; vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; + columnDisplayAction.show = angular.isDefined(vm.settings.enableSelectColumnDisplay) ? vm.settings.enableSelectColumnDisplay : true; + statusFilterAction.show = angular.isDefined(vm.settings.enableStatusFilter) ? vm.settings.enableStatusFilter : true; if (!vm.allowAcknowledgment && !vm.allowClear) { vm.enableSelection = false; } diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html index 802ea5de99..03e2289912 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html +++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html @@ -63,28 +63,7 @@ {{ key.title }} - - - filter_list - - - {{'alarm.alarm-status-filter' | translate}} - - - - view_column - - - {{'entity.columns-to-display' | translate}} - - - + diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js index c7ff63b00f..4da445ea0f 100644 --- a/ui/src/app/widget/lib/entities-table-widget.js +++ b/ui/src/app/widget/lib/entities-table-widget.js @@ -91,6 +91,15 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP icon: 'search' }; + let columnDisplayAction = { + name: 'entity.columns-to-display', + show: true, + onAction: function($event) { + vm.editColumnsToDisplay($event); + }, + icon: 'view_column' + }; + vm.enterFilterMode = enterFilterMode; vm.exitFilterMode = exitFilterMode; vm.onReorder = onReorder; @@ -147,7 +156,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP function initializeConfig() { - vm.ctx.widgetActions = [ vm.searchAction ]; + vm.ctx.widgetActions = [ vm.searchAction, columnDisplayAction ]; vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); @@ -162,6 +171,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true; vm.displayEntityLabel = angular.isDefined(vm.settings.displayEntityLabel) ? vm.settings.displayEntityLabel : false; + columnDisplayAction.show = angular.isDefined(vm.settings.enableSelectColumnDisplay) ? vm.settings.enableSelectColumnDisplay : true; if (vm.settings.entityNameColumnTitle && vm.settings.entityNameColumnTitle.length) { vm.entityNameColumnTitle = utils.customTranslation(vm.settings.entityNameColumnTitle, vm.settings.entityNameColumnTitle); diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html index 5f14623bcb..0ddfb9bd43 100644 --- a/ui/src/app/widget/lib/entities-table-widget.tpl.html +++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html @@ -42,18 +42,7 @@ {{ column.title }} - - - view_column - - - {{'entity.columns-to-display' | translate}} - - - + From c36af7311ef46d64a0396525045ac88d17ff048e Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 17 Oct 2019 18:25:05 +0300 Subject: [PATCH 021/120] Add bulk provision support label (#2096) * Add to asset support label * Add support import label * Add support update entity type and label * Add translate asset label --- .../install/ThingsboardInstallService.java | 5 +++ .../CassandraDatabaseUpgradeService.java | 9 +++++ .../install/SqlDatabaseUpgradeService.java | 9 +++++ .../server/common/data/asset/Asset.java | 12 ++++++ .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/nosql/AssetEntity.java | 8 +++- .../server/dao/model/sql/AssetEntity.java | 8 +++- .../resources/cassandra/schema-entities.cql | 3 +- .../main/resources/sql/schema-entities.sql | 1 + ui/src/app/api/entity.service.js | 38 +++++++++++++------ ui/src/app/asset/asset-fieldset.tpl.html | 4 ++ ui/src/app/common/types.constant.js | 4 ++ .../import-dialog-csv.controller.js | 6 ++- .../table-columns-assignment.directive.js | 6 +++ .../table-columns-assignment.tpl.html | 1 + ui/src/app/locale/locale.constant-cs_CZ.json | 3 +- ui/src/app/locale/locale.constant-de_DE.json | 3 +- ui/src/app/locale/locale.constant-en_US.json | 4 +- ui/src/app/locale/locale.constant-es_ES.json | 3 +- ui/src/app/locale/locale.constant-fa_IR.json | 3 +- ui/src/app/locale/locale.constant-fr_FR.json | 3 +- ui/src/app/locale/locale.constant-it_IT.json | 3 +- ui/src/app/locale/locale.constant-ja_JA.json | 3 +- ui/src/app/locale/locale.constant-ru_RU.json | 4 +- ui/src/app/locale/locale.constant-tr_TR.json | 3 +- ui/src/app/locale/locale.constant-uk_UA.json | 6 ++- ui/src/app/locale/locale.constant-zh_CN.json | 3 +- 27 files changed, 127 insertions(+), 29 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index f384817465..99f31323f2 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -119,6 +119,11 @@ public class ThingsboardInstallService { case "2.4.0": log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ..."); + case "2.4.1": + log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); + + databaseUpgradeService.upgradeDatabase("2.4.1"); + log.info("Updating system data..."); systemDataLoaderService.deleteSystemWidgetBundle("charts"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java index c73c12e65f..68ca728823 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java @@ -267,6 +267,15 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { } catch (InvalidQueryException e) {} log.info("Schema updated."); break; + case "2.4.1": + log.info("Updating schema ..."); + String updateAssetTableStmt = "alter table asset add label text"; + try { + cluster.getSession().execute(updateAssetTableStmt); + Thread.sleep(2500); + } catch (InvalidQueryException e) {} + log.info("Schema updated."); + break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index d086e4be8b..e87fd0fce7 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -176,6 +176,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Schema updated."); } break; + case "2.4.1": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) {} + log.info("Schema updated."); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index 174044de28..c409cd2a37 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -31,6 +31,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements private CustomerId customerId; private String name; private String type; + private String label; public Asset() { super(); @@ -46,6 +47,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements this.customerId = asset.getCustomerId(); this.name = asset.getName(); this.type = asset.getType(); + this.label = asset.getLabel(); } public TenantId getTenantId() { @@ -81,6 +83,14 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements this.type = type; } + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + @Override public String getSearchText() { return getName(); @@ -97,6 +107,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements builder.append(name); builder.append(", type="); builder.append(type); + builder.append(", label="); + builder.append(label); builder.append(", additionalInfo="); builder.append(getAdditionalInfo()); builder.append(", createdTime="); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index f065443489..c466c034fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -197,6 +197,7 @@ public class ModelConstants { public static final String ASSET_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ASSET_NAME_PROPERTY = "name"; public static final String ASSET_TYPE_PROPERTY = "type"; + public static final String ASSET_LABEL_PROPERTY = "label"; public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java index a951b970f3..8313b44afe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java @@ -37,6 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @@ -64,6 +65,9 @@ public final class AssetEntity implements SearchTextEntity { @Column(name = ASSET_NAME_PROPERTY) private String name; + @Column(name = ASSET_LABEL_PROPERTY) + private String label; + @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; @@ -86,6 +90,7 @@ public final class AssetEntity implements SearchTextEntity { } this.name = asset.getName(); this.type = asset.getType(); + this.label = asset.getLabel(); this.additionalInfo = asset.getAdditionalInfo(); } @@ -163,8 +168,9 @@ public final class AssetEntity implements SearchTextEntity { } asset.setName(name); asset.setType(type); + asset.setLabel(label); asset.setAdditionalInfo(additionalInfo); return asset; } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java index 043a02b813..bfc8020a7b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java @@ -40,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @@ -61,6 +62,9 @@ public final class AssetEntity extends BaseSqlEntity implements SearchTex @Column(name = ASSET_TYPE_PROPERTY) private String type; + @Column(name = ASSET_LABEL_PROPERTY) + private String label; + @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; @@ -84,6 +88,7 @@ public final class AssetEntity extends BaseSqlEntity implements SearchTex } this.name = asset.getName(); this.type = asset.getType(); + this.label = asset.getLabel(); this.additionalInfo = asset.getAdditionalInfo(); } @@ -113,8 +118,9 @@ public final class AssetEntity extends BaseSqlEntity implements SearchTex } asset.setName(name); asset.setType(type); + asset.setLabel(label); asset.setAdditionalInfo(additionalInfo); return asset; } -} \ No newline at end of file +} diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql index 611c08d5ef..a07e27cbe1 100644 --- a/dao/src/main/resources/cassandra/schema-entities.cql +++ b/dao/src/main/resources/cassandra/schema-entities.cql @@ -244,6 +244,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.asset ( customer_id timeuuid, name text, type text, + label text, search_text text, additional_info text, PRIMARY KEY (id, tenant_id, customer_id, type) @@ -711,4 +712,4 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_ent AND search_text IS NOT NULL AND id IS NOT NULL PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type) - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); \ No newline at end of file + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 2903ca2fb1..089ed28afc 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS asset ( additional_info varchar, customer_id varchar(31), name varchar(255), + label varchar(255), search_text varchar(255), tenant_id varchar(31), type varchar(255) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index f39615c933..5ce4fd7108 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -1130,19 +1130,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device let statisticalInfo = {}; let newEntity = { name: entityParameters.name, - type: entityParameters.type + type: entityParameters.type, + label: entityParameters.label }; - let promise; - switch (entityType) { - case types.entityType.device: - promise = deviceService.saveDevice(newEntity, config); - break; - case types.entityType.asset: - promise = assetService.saveAsset(newEntity, true, config); - break; - } + let saveEntityPromise = getEntitySavePromise(entityType, newEntity, config); - promise.then(function success(response) { + saveEntityPromise.then(function success(response) { saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { statisticalInfo.create = { entity: 1 @@ -1166,7 +1159,15 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device break; } findIdEntity.then(function success(response) { - saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { + let promises = []; + if(response.label !== entityParameters.label || response.type !== entityParameters.type){ + response.label = entityParameters.label; + response.type = entityParameters.type; + promises.push(getEntitySavePromise(entityType, response, config)); + } + promises.push(saveEntityRelation(entityType, response.id, entityParameters, config)); + + $q.all(promises).then(function success() { statisticalInfo.update = { entity: 1 }; @@ -1193,6 +1194,19 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } + function getEntitySavePromise(entityType, newEntity, config) { + let promise; + switch (entityType) { + case types.entityType.device: + promise = deviceService.saveDevice(newEntity, config); + break; + case types.entityType.asset: + promise = assetService.saveAsset(newEntity, true, config); + break; + } + return promise; + } + function getRelatedEntity(entityId, keys, typeTranslatePrefix) { var deferred = $q.defer(); getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then( diff --git a/ui/src/app/asset/asset-fieldset.tpl.html b/ui/src/app/asset/asset-fieldset.tpl.html index cc5b14c3cc..1514c90d8a 100644 --- a/ui/src/app/asset/asset-fieldset.tpl.html +++ b/ui/src/app/asset/asset-fieldset.tpl.html @@ -63,6 +63,10 @@ ng-model="asset.type" entity-type="types.entityType.asset"> + + + + diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 37ccd91302..e6e65bbecf 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -369,6 +369,10 @@ export default angular.module('thingsboard.types', []) name: 'import.column-type.type', value: 'type' }, + label: { + name: 'import.column-type.label', + value: 'label' + }, clientAttribute: { name: 'import.column-type.client-attribute', value: 'CLIENT_ATTRIBUTE' diff --git a/ui/src/app/import-export/import-dialog-csv.controller.js b/ui/src/app/import-export/import-dialog-csv.controller.js index a7b5329205..e11f59c5b4 100644 --- a/ui/src/app/import-export/import-dialog-csv.controller.js +++ b/ui/src/app/import-export/import-dialog-csv.controller.js @@ -98,7 +98,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo vm.columnsParam = []; var columnParam = {}; for (var i = 0; i < parseData.headers.length; i++) { - if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type)$/im) === 0) { + if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type|label)$/im) === 0) { columnParam = { type: types.importEntityColumnType[parseData.headers[i].toLowerCase()].value, key: parseData.headers[i].toLowerCase(), @@ -126,6 +126,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo var entityData = { name: "", type: "", + label: "", accessToken: "", attributes: { server: [], @@ -162,6 +163,9 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo case types.importEntityColumnType.type.value: entityData.type = importData.rows[i][j]; break; + case types.importEntityColumnType.label.value: + entityData.label = importData.rows[i][j]; + break; } } entitiesData.push(entityData); diff --git a/ui/src/app/import-export/table-columns-assignment.directive.js b/ui/src/app/import-export/table-columns-assignment.directive.js index a645e07392..5d96ba1c83 100644 --- a/ui/src/app/import-export/table-columns-assignment.directive.js +++ b/ui/src/app/import-export/table-columns-assignment.directive.js @@ -44,6 +44,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { vm.columnTypes.name = types.importEntityColumnType.name; vm.columnTypes.type = types.importEntityColumnType.type; + vm.columnTypes.label = types.importEntityColumnType.label; switch (vm.entityType) { case types.entityType.device: @@ -62,6 +63,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { if (newVal) { var isSelectName = false; var isSelectType = false; + var isSelectLabel = false; var isSelectCredentials = false; for (var i = 0; i < newVal.length; i++) { switch (newVal[i].type) { @@ -71,6 +73,9 @@ function TableColumnsAssignmentController($scope, types, $timeout) { case types.importEntityColumnType.type.value: isSelectType = true; break; + case types.importEntityColumnType.label.value: + isSelectLabel = true; + break; case types.importEntityColumnType.accessToken.value: isSelectCredentials = true; break; @@ -84,6 +89,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { $timeout(function () { vm.columnTypes.name.disable = isSelectName; vm.columnTypes.type.disable = isSelectType; + vm.columnTypes.label.disable = isSelectLabel; if (angular.isDefined(vm.columnTypes.accessToken)) { vm.columnTypes.accessToken.disable = isSelectCredentials; } diff --git a/ui/src/app/import-export/table-columns-assignment.tpl.html b/ui/src/app/import-export/table-columns-assignment.tpl.html index f00a08251c..b1eeafb8a7 100644 --- a/ui/src/app/import-export/table-columns-assignment.tpl.html +++ b/ui/src/app/import-export/table-columns-assignment.tpl.html @@ -41,6 +41,7 @@ Date: Thu, 17 Oct 2019 18:25:28 +0300 Subject: [PATCH 022/120] Added missing UA & RU translates (#2038) --- ui/src/app/locale/locale.constant-ru_RU.json | 46 ++++++++++++++++++-- ui/src/app/locale/locale.constant-uk_UA.json | 46 ++++++++++++++++++-- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index c8ce340bb4..f95259c47c 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -85,7 +85,28 @@ "timeout-required": "Таймаут обязателен.", "timeout-invalid": "Недействительный таймаут.", "enable-tls": "Включить TLS", - "send-test-mail": "Отправить пробное письмо" + "send-test-mail": "Отправить пробное письмо", + "security-settings": "Настройки безопасности", + "password-policy": "Политика паролей", + "minimum-password-length": "Минимальная длина пароля", + "minimum-password-length-required": "Требуется минимальная длина пароля", + "minimum-password-length-range": "Минимальная длина пароля должна быть в диапазоне от 5 до 50", + "minimum-uppercase-letters": "Минимальное количество прописных букв", + "minimum-uppercase-letters-range": "Минимальное количество прописных букв не может быть отрицательным", + "minimum-lowercase-letters": "Минимальное количество строчных букв", + "minimum-lowercase-letters-range": "Минимальное количество строчных букв не может быть отрицательным", + "minimum-digits": "Минимальное количество цифр", + "minimum-digits-range": "Минимальное количество цифр не может быть отрицательным", + "minimum-special-characters": "Минимальное количество специальных символов", + "minimum-special-characters-range": "Минимальное количество специальных символов не может быть отрицательным", + "password-expiration-period-days": "Срок действия пароля в днях", + "password-expiration-period-days-range": "Срок действия пароля в днях не может быть отрицательным", + "password-reuse-frequency-days": "Частота повторного использования пароля в днях", + "password-reuse-frequency-days-range": "Частота повторного использования пароля в днях не может быть отрицательной", + "general-policy": "Общая политика", + "max-failed-login-attempts": "Максимальное количество неудачных попыток входа в систему, прежде чем учетная запись заблокирована", + "minimum-max-failed-login-attempts-range": "Максимальное количество неудачных попыток входа в систему не может быть отрицательным", + "user-lockout-notification-email": "В случае блокировки учетной записи пользователя отправьте уведомление на электронную почту" }, "alarm": { "alarm": "Оповещение", @@ -308,6 +329,9 @@ "type-relations-delete": "Удалены все отношения", "type-alarm-ack": "Подтвержден", "type-alarm-clear": "Сброшен", + "type-login": "Вход", + "type-logout": "Выход", + "type-lockout": "Заблокирован", "status-success": "Успех", "status-failure": "Сбой", "audit-log-details": "Подробности аудит лога", @@ -1205,6 +1229,7 @@ }, "profile": { "profile": "Профиль", + "last-login-time": "Время последнего входа в систему", "change-password": "Изменить пароль", "current-password": "Текущий пароль" }, @@ -1445,7 +1470,11 @@ "activation-link-copied-message": "Ссылка для активации пользователя скопировано в буфер обмена", "details": "Подробности", "login-as-tenant-admin": "Войти как администратор владельца", - "login-as-customer-user": "Войти как пользователь клиента" + "login-as-customer-user": "Войти как пользователь клиента", + "disable-account": "Отключить учетную запись пользователя", + "enable-account": "Включить учетную запись пользователя", + "enable-account-message": "Учетная запись пользователя была успешно включена!", + "disable-account-message": "Учетная запись пользователя была успешно отключена!" }, "value": { "type": "Тип значения", @@ -1674,6 +1703,7 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете", + "date": "Дата", "blocked-location": "Геолокация заблокирована в вашем браузере", "claim-device": "Подтвердить устройство", "claim-failed": "Не удалось подтвердить устройство!", @@ -1690,14 +1720,20 @@ "longitude": "Долгота", "not-allowed-entity": "Выбраный объект не имеет общих атрибутов", "no-attribute-selected": "Атрибут не выбран", + "no-datakey-selected": "Ни один datakey не выбран", "no-entity-selected": "Объект не выбран", "no-coordinate-specified": "Ключ для широты/долготы не указан", "no-support-geolocation": "Ваш браузер не поддерживает геолокацию", + "no-image": "Нет изображения", + "no-support-web-camera": "Нет поддерживаемой веб-камеры", "no-timeseries-selected": "Параметр телеметрии не выбран", "secret-key": "Секретный ключ", "secret-key-required": "Необходимо указать секретный ключ", "switch-attribute-value": "Изменить значение атрибута", + "switch-camera": "Изменить камеру", "switch-timeseries-value": "Изменить значение телеметрии", + "take-photo": "Сделать фото", + "time": "Время", "timeseries-not-allowed": "Телеметрия не может быть выбрана в этом виджете", "update-failed": "Не удалось обновить", "update-successful": "Успешно обновлено", @@ -1718,7 +1754,11 @@ "row-click": "Действий при щелчке на строку", "marker-click": "Действия при щелчке на маркер", "polygon-click": "Действия при щелчке на полигон", - "tooltip-tag-action": "Действие при нажатии на ссылку в подсказке" + "tooltip-tag-action": "Действие при нажатии на ссылку в подсказке", + "node-selected": "Действий при выборе ноды", + "element-click": "Действий при щелчке на HTML элементе", + "pie-slice-click": "Действий при щелчке на секции круговой диаграммы", + "row-double-click": "Действий при двойном щелчке на строку" } }, "language": { diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 3cc4f0b9ad..e5f89c11c3 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -101,7 +101,28 @@ "password-was-reset": "Пароль було надіслано повідомленням" }, "mail-subject": "Тема повідомлення", - "mail-body": "Вміст повідомлення" + "mail-body": "Вміст повідомлення", + "security-settings": "Налаштування безпеки", + "password-policy": "Політика щодо паролів", + "minimum-password-length": "Мінімальна довжина пароля", + "minimum-password-length-required": "Потрібна мінімальна довжина пароля", + "minimum-password-length-range": "Мінімальна довжина пароля повинна бути в межах від 5 до 50", + "minimum-uppercase-letters": "Мінімальна кількість великих літер", + "minimum-uppercase-letters-range": "Мінімальна кількість великих літер не може бути негативною", + "minimum-lowercase-letters": "Мінімальна кількість малих літер", + "minimum-lowercase-letters-range": "Мінімальна кількість малих літер не може бути негативною", + "minimum-digits": "Мінімальна кількість цифр", + "minimum-digits-range": "Мінімальна кількість цифр не може бути негативною", + "minimum-special-characters": "Мінімальна кількість спеціальних символів", + "minimum-special-characters-range": "Мінімальна кількість спеціальних символів не може бути негативною", + "password-expiration-period-days": "Термін дії пароля в днях", + "password-expiration-period-days-range": "Термін дії пароля в днях не може бути негативним", + "password-reuse-frequency-days": "Частота повторного використання пароля в днях", + "password-reuse-frequency-days-range": "Частота повторного використання пароля в днях не може бути негативною", + "general-policy": "Загальна політика", + "max-failed-login-attempts": "Максимальна кількість невдалих спроб входу, перш ніж обліковий запис заблоковано", + "minimum-max-failed-login-attempts-range": "Максимальна кількість невдалих спроб входу не може бути негативною", + "user-lockout-notification-email": "У разі блокування облікового запису користувача, надішліть сповіщення на електронну пошту" }, "alarm": { "alarm": "Сигнал тривоги", @@ -341,6 +362,9 @@ "type-relations-delete": "Всі відношення видалено", "type-alarm-ack": "Визнано", "type-alarm-clear": "Очищено", + "type-login": "Вхід", + "type-logout": "Вихід", + "type-lockout": "Заблокований", "type-rest-api-rule-engine-call": "Rule engine REST API call", "status-success": "Успішно", "status-failure": "Невдало", @@ -1620,6 +1644,7 @@ }, "profile": { "profile": "Профіль", + "last-login-time": "Час останнього входу", "change-password": "Змінити пароль", "current-password": "Поточний пароль" }, @@ -2015,7 +2040,11 @@ "search": "Пошук користувачів", "details": "Подробиці", "login-as-tenant-admin": "Увійти як адміністратор власника", - "login-as-customer-user": "Увійти як користувач клієнта" + "login-as-customer-user": "Увійти як користувач клієнта", + "disable-account": "Вимкнути обліковий запис користувача", + "enable-account": "Увімкнути обліковий запис користувача", + "enable-account-message": "Обліковий запис користувача успішно увімкнено!", + "disabled-account-message": "Обліковий запис користувача успішно вимкнено!" }, "value": { "type": "Тип значення", @@ -2248,6 +2277,7 @@ }, "input-widgets": { "attribute-not-allowed": "Атрибут не може бути вибраний в цьому віджеті", + "date": "Дата", "blocked-location": "Геолокація заблокована у вашому браузері", "claim-device": "Підтвердити пристрій", "claim-failed": "Не вдалося підтвердити пристрій!", @@ -2264,14 +2294,20 @@ "longitude": "Довгота", "not-allowed-entity": "Обрана сутність не має спільних атрибутів", "no-attribute-selected": "Атрибут не вибрано", + "no-datakey-selected": "Ні один datakey не обраний", "no-entity-selected": "Сутність не вибрано", "no-coordinate-specified": "Ключ для широти/довготи не вказаний", "no-support-geolocation": "Ваш браузер не підтримує геолокацію", + "no-image": "Немає зображення", + "no-support-web-camera": "Нет поддерживаемой веб-камеры", "no-timeseries-selected": "Параметр телеметрії не вибрано", "secret-key": "Секретний ключ", "secret-key-required": "Необхідно вказати секретний ключ", "switch-attribute-value": "Змінити значення атрибута", + "switch-camera": "Змінити камеру", "switch-timeseries-value": "Змінити значення телеметрії", + "take-photo": "Зробити фото", + "time": "Час", "timeseries-not-allowed": "Телеметрія не може бути вибрана в цьому віджеті", "update-failed": "Не вдалося оновити", "update-successful": "Успішно оновлено", @@ -2324,7 +2360,11 @@ "row-click": "Клацніть на рядок", "marker-click": "Клацніть на маркер", "polygon-click": "Дія при натисканні на полігон", - "tooltip-tag-action": "Дія при натисканні на посилання в підказці" + "tooltip-tag-action": "Дія при натисканні на посилання в підказці", + "node-selected": "Дії при виборі ноди", + "element-click": "Дії при натисканні на HTML елементі", + "pie-slice-click": "Дії при натисканні на секції кругової діаграми", + "row-double-click": "Дії при подвійному натисканні на рядок" } }, "language": { From 25e36583f8b983cea524df7a9e22f22d7d1b0538 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 17 Oct 2019 18:26:10 +0300 Subject: [PATCH 023/120] Feature/clustering market (#2050) * Add support clustering and creating setting schema from google and tencent * Add settings for leaflet * Fix name setting * Fix text setting and change zoom level --- ui/package-lock.json | 357 ++++++++++++------------ ui/package.json | 1 + ui/src/app/widget/lib/google-map.js | 23 +- ui/src/app/widget/lib/map-widget2.js | 169 ++++++++++- ui/src/app/widget/lib/openstreet-map.js | 19 +- ui/src/app/widget/lib/tencent-map.js | 16 +- 6 files changed, 387 insertions(+), 198 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index cbe0a07093..dab90f1904 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -39,7 +39,7 @@ "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "integrity": "sha1-BuKrGb21NThVWaq7W6WXKUgoAPg=", "dev": true, "requires": { "@babel/highlight": "^7.0.0" @@ -277,7 +277,7 @@ "@babel/helper-function-name": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "integrity": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", @@ -288,7 +288,7 @@ "@babel/helper-get-function-arity": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "integrity": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -669,7 +669,7 @@ "@babel/highlight": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "integrity": "sha1-9xDDjI1Fjm3ZogGvtjf8t4HOmeQ=", "dev": true, "requires": { "chalk": "^2.0.0", @@ -680,7 +680,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -706,7 +706,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -1661,12 +1661,12 @@ "@flowjs/ng-flow": { "version": "2.7.8", "resolved": "https://registry.npmjs.org/@flowjs/ng-flow/-/ng-flow-2.7.8.tgz", - "integrity": "sha512-zO6jNvz41oMOJj9+1N+vLT0ytitbCtuGABJQRzQDOPXyRMmlSXfJ7om5oYOztyUFrr4jDpE4QFPt+r2/RFceCg==" + "integrity": "sha1-HZ+dH4Ks2lNgMowxW6z9YNv9mBk=" }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=", "dev": true, "requires": { "call-me-maybe": "^1.0.1", @@ -1912,7 +1912,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" }, "accepts": { "version": "1.3.7", @@ -2018,7 +2018,7 @@ "angular-carousel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/angular-carousel/-/angular-carousel-1.1.0.tgz", - "integrity": "sha512-UiLMgT7Ueqk4xpliF1gWt4dYKXezdJA1jyZPNsUWkOGO/dwLuKi284h3BgWl4CnaH7kEBw8L2gsBOyqbYaumNQ==" + "integrity": "sha1-PmlA5ovRio85L8Qx2XGSrDSIMdE=" }, "angular-cookies": { "version": "1.5.8", @@ -2039,7 +2039,7 @@ } }, "angular-fullscreen": { - "version": "git://github.com/fabiobiondi/angular-fullscreen.git#119b7fbac911d154fd56ace38ebe3432475e8a20", + "version": "git://github.com/fabiobiondi/angular-fullscreen.git#8217174565761d3566807bc60a73b5ca015b8cb6", "from": "git://github.com/fabiobiondi/angular-fullscreen.git#master" }, "angular-gridster": { @@ -2113,7 +2113,7 @@ "angular-translate": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", - "integrity": "sha512-Mw0kFBqsv5j8ItL9IhRZunIlVmIRW6iFsiTmRs9wGr2QTt8z4rehYlWyHos8qnXc/kyOYJiW50iH50CSNHGB9A==", + "integrity": "sha1-sp7Q0vm6xEB156rTKEFmxZ4VB5E=", "requires": { "angular": ">=1.2.26 <=1.7" } @@ -2121,7 +2121,7 @@ "angular-translate-handler-log": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-handler-log/-/angular-translate-handler-log-2.18.1.tgz", - "integrity": "sha512-TyKzCW4GubNazwCgLpCVXd2212CWdZOckf+aL5+gLuThPhVpOvlg18RSmz8MNPto3kwCcCw3LzShlZ6RX/MQRA==", + "integrity": "sha1-icu1mCeALYb4EVJ1+/iNbYiWsNQ=", "requires": { "angular-translate": "~2.18.1" } @@ -2129,7 +2129,7 @@ "angular-translate-interpolation-messageformat": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-interpolation-messageformat/-/angular-translate-interpolation-messageformat-2.18.1.tgz", - "integrity": "sha512-SlmyxLB/UUy7FWoGx5QJHrhq8fUu/xzCR0h/ngexOtXZopQjs1vm+TrFZ69d4c/LI7C91sfP4mq4ES29o1xCxA==", + "integrity": "sha1-FsUq4MYcJA8PJBZKBSGUPPi6QI4=", "requires": { "angular-translate": "~2.18.1", "messageformat": "~1.0.2" @@ -2138,7 +2138,7 @@ "angular-translate-loader-static-files": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-loader-static-files/-/angular-translate-loader-static-files-2.18.1.tgz", - "integrity": "sha512-5MuyzAROfc493kjLjKlLGLBzXiRmZIFbcWZGutDRxW5SRXSpwrH0u0hh0ENNnUyUQbe2vUspHNPIuZqlq8qIhw==", + "integrity": "sha1-rQw8iDsYsIm9uNsCu9Nm2QP4V8w=", "requires": { "angular-translate": "~2.18.1" } @@ -2146,7 +2146,7 @@ "angular-translate-storage-cookie": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-storage-cookie/-/angular-translate-storage-cookie-2.18.1.tgz", - "integrity": "sha512-wiMaF/0OGN/3ilaYunfsqdLNpfGZEJK0fj4zT8yjD3XPq7Q9kM88xZ4XJiWKgodZShBljGCRzqgQbKMF7d1MLw==", + "integrity": "sha1-j8vaspb6gkkOALQorxp0ahf0QVY=", "requires": { "angular-cookies": ">=1.2.26 <1.8", "angular-translate": "~2.18.1" @@ -2155,7 +2155,7 @@ "angular-translate-storage-local": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-storage-local/-/angular-translate-storage-local-2.18.1.tgz", - "integrity": "sha512-zPxcbIJ8tdWXtWNKLtaswynKid0w5le6WPMwiLWhgKPnyzOp/y5WLBW+JEfnZnkGE24yOGhJ6jVPgRNzelLgzg==", + "integrity": "sha1-lHQP5NgBq3gpopofBeHDkFTIcwM=", "requires": { "angular-translate": "~2.18.1", "angular-translate-storage-cookie": "~2.18.1" @@ -2234,7 +2234,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", "dev": true }, "are-we-there-yet": { @@ -2250,7 +2250,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -2265,7 +2265,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-union": { @@ -2366,7 +2366,7 @@ }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -2426,7 +2426,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", "dev": true }, "attr-accept": { @@ -2681,7 +2681,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -2705,7 +2705,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2714,7 +2714,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2723,7 +2723,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -2740,7 +2740,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } @@ -2768,7 +2768,7 @@ "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "integrity": "sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=", "dev": true }, "binary-extensions": { @@ -2871,7 +2871,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3091,7 +3091,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -3130,7 +3130,7 @@ "dependencies": { "callsites": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } @@ -3290,13 +3290,13 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", "dev": true }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -3437,7 +3437,7 @@ "clone-regexp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", + "integrity": "sha1-BRgFzTMXM3XYIRj8CRhgbaOf1g8=", "dev": true, "requires": { "is-regexp": "^1.0.0", @@ -3609,7 +3609,7 @@ "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -3672,7 +3672,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { @@ -3699,7 +3699,7 @@ "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", "dev": true, "requires": { "aproba": "^1.1.1", @@ -3960,7 +3960,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", "dev": true }, "js-yaml": { @@ -4025,7 +4025,7 @@ "create-react-class": { "version": "15.6.3", "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "integrity": "sha1-LXMjf7P5cK5uvgEanmb0bbyoADY=", "requires": { "fbjs": "^0.8.9", "loose-envify": "^1.3.1", @@ -4192,7 +4192,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -4317,7 +4317,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -4327,7 +4327,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4336,7 +4336,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4345,7 +4345,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4362,7 +4362,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } @@ -4399,7 +4399,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" }, "delegates": { "version": "1.0.0", @@ -4464,7 +4464,7 @@ "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", "dev": true, "requires": { "pify": "^3.0.0" @@ -4564,7 +4564,7 @@ "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", "dev": true }, "domelementtype": { @@ -4595,7 +4595,7 @@ "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", "dev": true, "requires": { "is-obj": "^1.0.0" @@ -4690,7 +4690,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, "requires": { "once": "^1.4.0" @@ -4716,7 +4716,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", "dev": true, "requires": { "prr": "~1.0.1" @@ -5356,7 +5356,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -5365,7 +5365,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -5622,7 +5622,7 @@ "external-editor": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "integrity": "sha1-BFURz9jRM/OEZnPRBHwVTiFK09U=", "requires": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", @@ -6035,7 +6035,7 @@ "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", "dev": true }, "fs-write-stream-atomic": { @@ -6096,14 +6096,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6118,20 +6116,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -6248,8 +6243,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -6261,7 +6255,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6276,7 +6269,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6284,14 +6276,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6310,7 +6300,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6391,8 +6380,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6404,7 +6392,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6526,7 +6513,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6613,7 +6599,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", "dev": true }, "functional-red-black-tree": { @@ -6842,7 +6828,7 @@ }, "globby": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -6855,7 +6841,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -7999,7 +7985,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, "inline-style-prefixer": { "version": "2.0.5", @@ -8049,7 +8035,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -8122,7 +8108,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-callable": { @@ -8155,7 +8141,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -8166,7 +8152,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -8281,7 +8267,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -8335,7 +8321,7 @@ "is-supported-regexp-flag": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", - "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", + "integrity": "sha1-Ie4WUY0sHdPt0+mg1X5QIHrDZMo=", "dev": true }, "is-symbol": { @@ -8368,7 +8354,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "is-word-character": { @@ -8523,7 +8509,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", "dev": true }, "json-schema": { @@ -8540,7 +8526,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8667,6 +8653,11 @@ "resolved": "https://registry.npmjs.org/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz", "integrity": "sha1-RGf0n5jRv9VpWb2cZwUgPdJgEnc=" }, + "leaflet.markercluster": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz", + "integrity": "sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw==" + }, "less": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", @@ -8743,7 +8734,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -8849,7 +8840,7 @@ "lodash.merge": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=" }, "lodash.tail": { "version": "4.1.1", @@ -8865,7 +8856,7 @@ "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", "dev": true, "requires": { "chalk": "^2.0.1" @@ -8874,7 +8865,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -8894,7 +8885,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -9194,7 +9185,7 @@ "messageformat-parser": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz", - "integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==" + "integrity": "sha1-E7oiUKdrvejg/KDbs0dflcWUqQo=" }, "methods": { "version": "1.1.2", @@ -9244,7 +9235,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", "dev": true }, "mime-db": { @@ -9265,7 +9256,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=" }, "min-document": { "version": "2.19.0", @@ -9303,7 +9294,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "^1.1.7" } @@ -9316,7 +9307,7 @@ "minimist-options": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "integrity": "sha1-+6TIGRM54T7PTWG+sD8HAQPz2VQ=", "dev": true, "requires": { "arrify": "^1.0.1", @@ -9354,7 +9345,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -9450,7 +9441,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9481,7 +9472,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } @@ -9605,7 +9596,7 @@ } }, "ngFlowchart": { - "version": "git://github.com/thingsboard/ngFlowchart.git#ad172c26bb731f4e4e79d05dfa8cdc3f59cd1690", + "version": "git://github.com/thingsboard/ngFlowchart.git#1343a7478961f68280d81f0ecda4e722a2068e0f", "from": "git://github.com/thingsboard/ngFlowchart.git#master" }, "ngclipboard": { @@ -9665,7 +9656,7 @@ "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", "dev": true, "requires": { "lower-case": "^1.1.1" @@ -9684,7 +9675,7 @@ "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=", "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" @@ -10111,7 +10102,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -10440,7 +10431,7 @@ "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -10702,7 +10693,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -10901,7 +10892,7 @@ "postcss-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "integrity": "sha1-a5eUPkfHLYRfqeA/Jzdz1OjdbC0=", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -10913,7 +10904,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -10987,7 +10978,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { @@ -11063,7 +11054,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11094,13 +11085,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11126,7 +11117,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11157,13 +11148,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11184,7 +11175,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11215,13 +11206,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11241,7 +11232,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11272,13 +11263,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11373,7 +11364,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", "dev": true }, "process": { @@ -11397,7 +11388,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { "asap": "~2.0.3" } @@ -11477,7 +11468,7 @@ "pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -11555,7 +11546,7 @@ "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", + "integrity": "sha1-j99oIxz/qQvC+UYDkKDLdKKbKak=", "dev": true }, "randomatic": { @@ -11685,7 +11676,7 @@ "rc-menu": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-5.1.4.tgz", - "integrity": "sha512-ZUkUNda70GtTXcQDiO3rSDdk3sgIwDwzPUm5dVM8nRH/j84qv0BVBkIUwIBu8+s+G3G9lWLurRqh22dCqZPeOA==", + "integrity": "sha1-5d8I/ouDPoFGkTX/E7MKuPIf88Y=", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -11716,7 +11707,7 @@ "rc-trigger": { "version": "1.11.5", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-1.11.5.tgz", - "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", + "integrity": "sha1-+I+fhODnn44O8cjRv4rCIItxViA=", "requires": { "babel-runtime": "6.x", "create-react-class": "15.x", @@ -11880,7 +11871,7 @@ "react-transition-group": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", + "integrity": "sha1-4R9yslf5IbITIpp3TfRmEjRsfKY=", "requires": { "chain-function": "^1.0.0", "dom-helpers": "^3.2.0", @@ -11892,7 +11883,7 @@ "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "integrity": "sha1-wAATh15Vexzw39mjaKHD2rO1SN0=", "requires": { "lodash": "^4.0.1" } @@ -11921,7 +11912,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12271,12 +12262,12 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -12285,7 +12276,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -12450,7 +12441,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "integrity": "sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=", "dev": true }, "require-main-filename": { @@ -12543,7 +12534,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "retry": { @@ -12636,7 +12627,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "sass-graph": { "version": "2.2.4", @@ -12688,7 +12679,7 @@ "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=", "dev": true, "requires": { "ajv": "^6.1.0", @@ -13000,7 +12991,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -13036,7 +13027,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -13056,7 +13047,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13065,7 +13056,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13074,7 +13065,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -13091,7 +13082,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } @@ -13099,7 +13090,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -13108,7 +13099,7 @@ "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "integrity": "sha1-2Xa76ACve9IK4IWY1YI5NQiZPA0=", "dev": true, "requires": { "faye-websocket": "^0.10.0", @@ -13179,7 +13170,7 @@ "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", "dev": true, "requires": { "atob": "^2.1.1", @@ -13232,7 +13223,7 @@ "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -13326,7 +13317,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -13473,7 +13464,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -13497,7 +13488,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -13506,7 +13497,7 @@ "stringify-entities": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "integrity": "sha1-qYQX5Ucf0iez5F09sYYcEcr2aPc=", "dev": true, "requires": { "character-entities-html4": "^1.0.0", @@ -13628,7 +13619,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -13954,7 +13945,7 @@ "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", "dev": true, "requires": { "pify": "^3.0.0" @@ -14028,7 +14019,7 @@ "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" @@ -14037,7 +14028,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "strip-indent": { @@ -14049,7 +14040,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14145,7 +14136,7 @@ "stylelint-webpack-plugin": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-0.10.5.tgz", - "integrity": "sha512-jtYx3aJ2qDMvBMswe5NRPTO7kJgAKafc6GilAkWDp/ewoAmnoxA6TsYMnIPtLECRLwXevaCPvlh2JEUMGZCoUQ==", + "integrity": "sha1-C24NNz/14DuqgZfr4PJiWYG9Jms=", "dev": true, "requires": { "arrify": "^1.0.1", @@ -14169,7 +14160,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -14271,7 +14262,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -14282,7 +14273,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -14290,7 +14281,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -14349,7 +14340,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14358,7 +14349,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14367,7 +14358,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -14404,13 +14395,13 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -14442,7 +14433,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -14473,13 +14464,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14501,7 +14492,7 @@ "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" }, "table": { "version": "5.4.4", @@ -14736,7 +14727,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "requires": { "os-tmpdir": "~1.0.2" } @@ -14759,7 +14750,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -14965,7 +14956,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -15115,7 +15106,7 @@ "unified": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "integrity": "sha1-f71jD3GRJtZ9QMZEt+P2FwNfbbo=", "dev": true, "requires": { "bail": "^1.0.0", @@ -15189,7 +15180,7 @@ "unist-util-stringify-position": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "integrity": "sha1-Pzf881EnncvKdICrWIm7ioMu4cY=", "dev": true }, "unist-util-visit": { @@ -15277,7 +15268,7 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -15286,7 +15277,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", "dev": true } } @@ -15357,7 +15348,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", "dev": true }, "util": { @@ -15469,7 +15460,7 @@ "vfile": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "integrity": "sha1-5i2OcrIOg8MkvGxnJ47ickiL+Eo=", "dev": true, "requires": { "is-buffer": "^1.1.4", @@ -16558,7 +16549,7 @@ "websocket-extensions": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=", "dev": true }, "whatwg-fetch": { @@ -16654,7 +16645,7 @@ "ws": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "integrity": "sha1-y9nm514J/F0skAFfIfDECHXg3VE=", "requires": { "options": ">=0.0.5", "ultron": "1.0.x" diff --git a/ui/package.json b/ui/package.json index d43c0dda2c..466aa59618 100644 --- a/ui/package.json +++ b/ui/package.json @@ -65,6 +65,7 @@ "leaflet": "^1.5.1", "leaflet-polylinedecorator": "^1.6.0", "leaflet-providers": "^1.8.0", + "leaflet.markercluster": "^1.4.1", "material-steppers": "git://github.com/thingsboard/material-steppers.git#master", "material-ui": "^0.16.1", "material-ui-number-input": "^5.0.16", diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index d882056050..0cfdefd180 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -19,7 +19,7 @@ var gmGlobals = { } export default class TbGoogleMap { - constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, gmApiKey, gmDefaultMapType, defaultCenterPosition) { + constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, gmApiKey, gmDefaultMapType, defaultCenterPosition, markerClusteringSetting) { var tbMap = this; this.utils = utils; @@ -29,6 +29,7 @@ export default class TbGoogleMap { this.tooltips = []; this.defaultMapType = gmDefaultMapType; this.defaultCenterPosition = defaultCenterPosition; + this.isMarketCluster = markerClusteringSetting.isMarketCluster; function clearGlobalId() { if (gmGlobals.loadingGmId && gmGlobals.loadingGmId === tbMap.mapId) { @@ -49,6 +50,10 @@ export default class TbGoogleMap { zoom: tbMap.defaultZoomLevel || 8, center: new google.maps.LatLng(tbMap.defaultCenterPosition[0], tbMap.defaultCenterPosition[1]) // eslint-disable-line no-undef }); + if (tbMap.isMarketCluster){ + tbMap.markersCluster = new MarkerClusterer(tbMap.map, [], // eslint-disable-line no-undef + angular.merge({imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'}, markerClusteringSetting)); + } if (initCallback) { initCallback(); } @@ -87,7 +92,10 @@ export default class TbGoogleMap { this.initMapFunctionName = 'initGoogleMap_' + this.mapId; window[this.initMapFunctionName] = function() { // eslint-disable-line no-undef, angular/window-service - lazyLoad.load({ type: 'js', path: 'https://unpkg.com/@google/markerwithlabel@1.2.3/src/markerwithlabel.js' }).then( // eslint-disable-line no-undef + lazyLoad.load([ // eslint-disable-line no-undef + { type: 'js', path: 'https://unpkg.com/@google/markerwithlabel@1.2.3/src/markerwithlabel.js' }, + { type: 'js', path: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js' } + ]).then( function success() { gmGlobals.gmApiKeys[tbMap.apiKey].loaded = true; initGoogleMap(); @@ -105,6 +113,7 @@ export default class TbGoogleMap { ); }; + /* eslint-enable no-undef */ if (this.apiKey && this.apiKey.length > 0) { if (gmGlobals.gmApiKeys[this.apiKey]) { @@ -143,6 +152,10 @@ export default class TbGoogleMap { return angular.isDefined(this.map); } + getContainer() { + return this.isMarketCluster ? this.markersCluster : this.map; + } + /* eslint-disable no-undef */ updateMarkerLabel(marker, settings) { marker.set('labelContent', '
'+settings.labelText+'
'); @@ -240,7 +253,11 @@ export default class TbGoogleMap { if (settings.showLabel) { marker.set('labelAnchor', new google.maps.Point(100, iconInfo.size[1] + 20)); } - marker.setMap(gMap.map); + if(gMap.isMarketCluster) { + gMap.getContainer().addMarker(marker); + } else { + marker.setMap(gMap.getContainer()); + } }); if (settings.displayTooltip) { diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js index 1c34a57716..e6d2dfc0e7 100644 --- a/ui/src/app/widget/lib/map-widget2.js +++ b/ui/src/app/widget/lib/map-widget2.js @@ -48,7 +48,7 @@ export default class TbMapWidgetV2 { }; if (settings.defaultZoomLevel) { - if (settings.defaultZoomLevel > 0 && settings.defaultZoomLevel < 21) { + if (settings.defaultZoomLevel >= 0 && settings.defaultZoomLevel < 21) { this.defaultZoomLevel = Math.floor(settings.defaultZoomLevel); } } @@ -69,6 +69,46 @@ export default class TbMapWidgetV2 { var minZoomLevel = this.drawRoutes ? 18 : 15; + let markerClusteringSetting = { + isMarketCluster: false + }; + + if (settings.useClusterMarkers === true){ + if (mapProvider === 'google-map' || mapProvider === 'tencent-map') { + markerClusteringSetting = { + isMarketCluster: true, + zoomOnClick: settings.zoomOnClick, + averageCenter: true + }; + if(angular.isDefined(settings.maxZoom) && settings.maxZoom >= 0 && settings.maxZoom < 19){ + markerClusteringSetting.maxZoom = Math.floor(settings.maxZoom); + } + if(angular.isDefined(settings.gridSize) && settings.gridSize > 0){ + markerClusteringSetting.gridSize = Math.floor(settings.gridSize); + } + if(angular.isDefined(settings.minimumClusterSize) && settings.minimumClusterSize > 1){ + markerClusteringSetting.minimumClusterSize = Math.ceil(settings.minimumClusterSize); + } + } else if(mapProvider === 'openstreet-map' || mapProvider === 'here') { + markerClusteringSetting = { + isMarketCluster: true, + zoomToBoundsOnClick: settings.zoomOnClick, + showCoverageOnHover: settings.showCoverageOnHover, + removeOutsideVisibleBounds: settings.removeOutsideVisibleBounds, + animate: settings.animate, + chunkedLoading: settings.chunkedLoading + }; + if(angular.isDefined(settings.maxClusterRadius) && settings.maxClusterRadius > 0){ + markerClusteringSetting.maxClusterRadius = Math.floor(settings.maxClusterRadius); + } + if(angular.isDefined(settings.maxZoom) && settings.maxZoom >= 0 && settings.maxZoom < 19){ + markerClusteringSetting.disableClusteringAtZoom = Math.floor(settings.maxZoom); + } + } + } + + + var initCallback = function () { tbMap.update(); @@ -86,7 +126,7 @@ export default class TbMapWidgetV2 { let openStreetMapProvider = {}; if (mapProvider === 'google-map') { - this.map = new TbGoogleMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType, settings.defaultCenterPosition); + this.map = new TbGoogleMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType, settings.defaultCenterPosition, markerClusteringSetting); } else if (mapProvider === 'openstreet-map') { if (settings.useCustomProvider && settings.customProviderTileUrl) { openStreetMapProvider.name = settings.customProviderTileUrl; @@ -94,10 +134,10 @@ export default class TbMapWidgetV2 { } else { openStreetMapProvider.name = settings.mapProvider; } - this.map = new TbOpenStreetMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, openStreetMapProvider, null,settings.defaultCenterPosition); + this.map = new TbOpenStreetMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, openStreetMapProvider, null,settings.defaultCenterPosition, markerClusteringSetting); } else if (mapProvider === 'here') { openStreetMapProvider.name = settings.mapProvider; - this.map = new TbOpenStreetMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, openStreetMapProvider, settings.credentials, settings.defaultCenterPosition); + this.map = new TbOpenStreetMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, openStreetMapProvider, settings.credentials, settings.defaultCenterPosition, markerClusteringSetting); } else if (mapProvider === 'image-map') { this.map = new TbImageMap(this.ctx, $element, this.utils, initCallback, settings.mapImageUrl, @@ -107,7 +147,7 @@ export default class TbMapWidgetV2 { settings.imageUrlAttribute, settings.useDefaultCenterPosition ? settings.defaultCenterPosition: null); } else if (mapProvider === 'tencent-map') { - this.map = new TbTencentMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, settings.tmApiKey, settings.tmDefaultMapType, settings.defaultCenterPosition); + this.map = new TbTencentMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, settings.disableScrollZooming, minZoomLevel, settings.tmApiKey, settings.tmDefaultMapType, settings.defaultCenterPosition, markerClusteringSetting); } @@ -728,6 +768,24 @@ export default class TbMapWidgetV2 { "formIndex":schema.groupInfoes.length, "GroupTitle":"Route Map Settings" }); + } else if (mapProvider !== 'image-map'){ + angular.merge(schema.schema.properties, markerClusteringSettingsSchema.schema.properties); + schema.schema.required = schema.schema.required.concat(markerClusteringSettingsSchema.schema.required); + schema.form.push(markerClusteringSettingsSchema.form); + if (mapProvider === 'google-map' || mapProvider === 'tencent-map') { + angular.merge(schema.schema.properties, markerClusteringSettingsSchemaGoogle.schema.properties); + schema.schema.required = schema.schema.required.concat(markerClusteringSettingsSchemaGoogle.schema.required); + schema.form[schema.form.length -1] = schema.form[schema.form.length -1].concat(markerClusteringSettingsSchemaGoogle.form); + } + if (mapProvider === 'openstreet-map' || mapProvider === 'here') { + angular.merge(schema.schema.properties, markerClusteringSettingsSchemaLeaflet.schema.properties); + schema.schema.required = schema.schema.required.concat(markerClusteringSettingsSchemaLeaflet.schema.required); + schema.form[schema.form.length -1] = schema.form[schema.form.length -1].concat(markerClusteringSettingsSchemaLeaflet.form); + } + schema.groupInfoes.push({ + "formIndex":schema.groupInfoes.length, + "GroupTitle":"Markers Clustering Settings" + }); } return schema; } @@ -975,7 +1033,7 @@ const commonMapSettingsSchema = "type": "object", "properties": { "defaultZoomLevel": { - "title": "Default map zoom level (1 - 20)", + "title": "Default map zoom level (0 - 20)", "type": "number" }, "useDefaultCenterPosition": { @@ -1251,6 +1309,103 @@ const routeMapSettingsSchema = ] }; +const markerClusteringSettingsSchema = + { + "schema": { + "title": "Markers Clustering Configuration", + "type": "object", + "properties": { + "useClusterMarkers": { + "title": "Use map markers clustering", + "type": "boolean", + "default": false + }, + "zoomOnClick": { + "title": "Zoom when clicking on a cluster", + "type": "boolean", + "default": true + }, + "maxZoom": { + "title": "The maximum zoom level when a marker can be part of a cluster (0 - 18)", + "type": "number" + } + }, + "required": [] + }, + "form": [ + "useClusterMarkers", + "zoomOnClick", + "maxZoom" + ] + }; + +const markerClusteringSettingsSchemaGoogle = + { + "schema": { + "title": "Marker Clustering Configuration Google", + "type": "object", + "properties": { + "gridSize": { + "title": "Maximum radius that a cluster will cover in pixels", + "type": "number", + "default": 60 + }, + "minimumClusterSize": { + "title": "The minimum number of markers in a cluster", + "type": "number" + } + }, + "required": [] + }, + "form": [ + "gridSize", + "minimumClusterSize" + ] + }; + +const markerClusteringSettingsSchemaLeaflet = + { + "schema": { + "title": "Markers Clustering Configuration Leaflet", + "type": "object", + "properties": { + "showCoverageOnHover": { + "title": "Show the bounds of markers when mouse over a cluster", + "type": "boolean", + "default": true + }, + "animate": { + "title": "Show animation on markers when zooming", + "type": "boolean", + "default": true + }, + "maxClusterRadius": { + "title": "Maximum radius that a cluster will cover in pixels", + "type": "number", + "default": 80 + }, + "chunkedLoading": { + "title": "Use chunks for adding markers so that the page does not freeze", + "type": "boolean", + "default": false + }, + "removeOutsideVisibleBounds": { + "title": "Use lazy load for adding markers", + "type": "boolean", + "default": true + } + }, + "required": [] + }, + "form": [ + "showCoverageOnHover", + "animate", + "maxClusterRadius", + "chunkedLoading", + "removeOutsideVisibleBounds" + ] + }; + const imageMapSettingsSchema = { "schema": { @@ -1470,4 +1625,4 @@ const imageMapSettingsSchema = ] } ] - }; \ No newline at end of file + }; diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js index 21977392bf..abb6ccca31 100644 --- a/ui/src/app/widget/lib/openstreet-map.js +++ b/ui/src/app/widget/lib/openstreet-map.js @@ -14,18 +14,22 @@ * limitations under the License. */ import 'leaflet/dist/leaflet.css'; +import 'leaflet.markercluster/dist/MarkerCluster.css' +import 'leaflet.markercluster/dist/MarkerCluster.Default.css' import * as L from 'leaflet'; import 'leaflet-providers'; +import 'leaflet.markercluster/dist/leaflet.markercluster' export default class TbOpenStreetMap { - constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, mapProvider, credentials, defaultCenterPosition) { + constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, mapProvider, credentials, defaultCenterPosition, markerClusteringSetting) { this.utils = utils; this.defaultZoomLevel = defaultZoomLevel; this.dontFitMapBounds = dontFitMapBounds; this.minZoomLevel = minZoomLevel; this.tooltips = []; + this.isMarketCluster = markerClusteringSetting.isMarketCluster; if (!mapProvider) { mapProvider = { @@ -48,6 +52,11 @@ export default class TbOpenStreetMap { var tileLayer = mapProvider.isCustom ? L.tileLayer(mapProvider.name) : L.tileLayer.provider(mapProvider.name, credentials); tileLayer.addTo(this.map); + if (this.isMarketCluster) { + this.markersCluster = L.markerClusterGroup(markerClusteringSetting); + this.map.addLayer(this.markersCluster); + } + if (initCallback) { setTimeout(initCallback, 0); //eslint-disable-line } @@ -58,6 +67,10 @@ export default class TbOpenStreetMap { return angular.isDefined(this.map); } + getContainer() { + return this.isMarketCluster ? this.markersCluster : this.map; + } + updateMarkerLabel(marker, settings) { marker.unbindTooltip(); marker.bindTooltip('
' + settings.labelText + '
', @@ -147,7 +160,7 @@ export default class TbOpenStreetMap { marker.bindTooltip('
' + settings.labelText + '
', {className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset}); } - marker.addTo(opMap.map); + marker.addTo(opMap.getContainer()); }); if (settings.displayTooltip) { @@ -162,7 +175,7 @@ export default class TbOpenStreetMap { } removeMarker(marker) { - this.map.removeLayer(marker); + this.getContainer().removeLayer(marker); } createTooltip(marker, dsIndex, settings, markerArgs) { diff --git a/ui/src/app/widget/lib/tencent-map.js b/ui/src/app/widget/lib/tencent-map.js index ce24ad606b..2acead223d 100644 --- a/ui/src/app/widget/lib/tencent-map.js +++ b/ui/src/app/widget/lib/tencent-map.js @@ -19,7 +19,7 @@ var tmGlobals = { } export default class TbTencentMap { - constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, tmApiKey, tmDefaultMapType, defaultCenterPosition) { + constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, tmApiKey, tmDefaultMapType, defaultCenterPosition, markerClusteringSetting) { var tbMap = this; this.utils = utils; this.defaultZoomLevel = defaultZoomLevel; @@ -28,6 +28,7 @@ export default class TbTencentMap { this.tooltips = []; this.defaultMapType = tmDefaultMapType; this.defaultCenterPosition =defaultCenterPosition; + this.isMarketCluster = markerClusteringSetting.isMarketCluster; function clearGlobalId() { if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) { @@ -49,6 +50,13 @@ export default class TbTencentMap { center: new qq.maps.LatLng(tbMap.defaultCenterPosition[0],tbMap.defaultCenterPosition[1]) // eslint-disable-line no-undef }); + if (tbMap.isMarketCluster){ + tbMap.markersCluster = new qq.maps.MarkerCluster( // eslint-disable-line no-undef + angular.merge({map:tbMap.map}, markerClusteringSetting) + ); + } + + if (initCallback) { initCallback(); } @@ -238,7 +246,11 @@ export default class TbTencentMap { var tMap = this; this.createMarkerIcon(marker, settings, (iconInfo) => { marker.setIcon(iconInfo.icon); - marker.setMap(tMap.map); + if(tMap.isMarketCluster) { + tMap.markersCluster.addMarker(marker); + } else { + marker.setMap(tMap.map); + } if (settings.showLabel) { marker.label = new qq.maps.Label({ clickable: false, From 2b7df84ae9f3e77040f2e537072a95fc65d30aaf Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 18 Oct 2019 08:30:53 +0300 Subject: [PATCH 024/120] Claiming devices using claimData attribute (#2105) * Claiming devices using claimData attribute * Fixed license header --- .../server/dao/device/ClaimDataInfo.java | 30 ++++++++++ .../dao/device/ClaimDevicesService.java | 3 +- .../dao/device/ClaimDevicesServiceImpl.java | 55 +++++++++++++++---- 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java new file mode 100644 index 0000000000..3b3194dbbf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device; + +import lombok.Data; +import org.thingsboard.server.dao.device.claim.ClaimData; + +import java.util.List; + +@Data +public class ClaimDataInfo { + + private final boolean fromCache; + private final List key; + private final ClaimData data; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java index eb8c800d1e..a5f2dbcab1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java @@ -23,12 +23,13 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.device.claim.ClaimResult; import java.util.List; +import java.util.concurrent.ExecutionException; public interface ClaimDevicesService { ListenableFuture registerClaimingInfo(TenantId tenantId, DeviceId deviceId, String secretKey, long durationMs); - ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey); + ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException; ListenableFuture> reClaimDevice(TenantId tenantId, Device device); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java index 9000c91823..6955cd761c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.CustomerId; @@ -37,9 +39,12 @@ import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.model.ModelConstants; +import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CACHE; @@ -48,6 +53,8 @@ import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CA public class ClaimDevicesServiceImpl implements ClaimDevicesService { private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed"; + private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData"; + private static final ObjectMapper mapper = new ObjectMapper(); @Autowired private DeviceService deviceService; @@ -95,24 +102,45 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { }); } - @Override - public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) { + private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException { List key = constructCacheKey(device.getId()); + ClaimData claimDataFromCache = cache.get(key, ClaimData.class); + if (claimDataFromCache != null) { + return new ClaimDataInfo(true, key, claimDataFromCache); + } else { + Optional claimDataAttr = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME).get(); + if (claimDataAttr.isPresent()) { + try { + ClaimData claimDataFromAttribute = mapper.readValue(claimDataAttr.get().getValueAsString(), ClaimData.class); + return new ClaimDataInfo(false, key, claimDataFromAttribute); + } catch (IOException e) { + log.warn("Failed to read Claim Data [{}] from attribute!", claimDataAttr, e); + } + } + } + return null; + } + + @Override + public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException { Cache cache = cacheManager.getCache(CLAIM_DEVICES_CACHE); - ClaimData claimData = cache.get(key, ClaimData.class); + ClaimDataInfo claimData = getClaimData(cache, device); if (claimData != null) { long currTs = System.currentTimeMillis(); - if (currTs > claimData.getExpirationTime() || !secretKey.equals(claimData.getSecretKey())) { + if (currTs > claimData.getData().getExpirationTime() || !secretKeyIsEmptyOrEqual(secretKey, claimData.getData().getSecretKey())) { log.warn("The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]", device.getName()); - cache.evict(key); + if (claimData.isFromCache()) { + cache.evict(claimData.getKey()); + } return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); } else { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { device.setCustomerId(customerId); Device savedDevice = deviceService.saveDevice(device); - return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS)); } - return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED)); } } else { log.warn("Failed to find the device's claiming message![{}]", device.getName()); @@ -124,6 +152,10 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { } } + private boolean secretKeyIsEmptyOrEqual(String secretKeyA, String secretKeyB) { + return (StringUtils.isEmpty(secretKeyA) && StringUtils.isEmpty(secretKeyB)) || secretKeyA.equals(secretKeyB); + } + @Override public ListenableFuture> reClaimDevice(TenantId tenantId, Device device) { if (!device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { @@ -159,13 +191,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { return systemDurationMs; } - private ListenableFuture> removeClaimingSavedData(Cache cache, List key, Device device) { - cache.evict(key); - if (isAllowedClaimingByDefault) { - return Futures.immediateFuture(null); + private ListenableFuture> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) { + if (data.isFromCache()) { + cache.evict(data.getKey()); } return attributesService.removeAll(device.getTenantId(), - device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(CLAIM_ATTRIBUTE_NAME)); + device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME)); } private void cacheEviction(DeviceId deviceId) { From 38d7c1e8260ce2c7e778a02822df9222be287878 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 18 Oct 2019 08:33:09 +0300 Subject: [PATCH 025/120] Redis cluster configuration support --- .../src/main/resources/thingsboard.yml | 35 +++++++- .../dao/cache/TBRedisCacheConfiguration.java | 65 ++++++++++---- .../cache/TBRedisClusterConfiguration.java | 77 ++++++++++++++++ .../cache/TBRedisStandaloneConfiguration.java | 88 +++++++++++++++++++ 4 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 48e37dd646..5c55dc4249 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -278,10 +278,41 @@ redis: # standalone or cluster connection: type: standalone + standalone: host: "${REDIS_HOST:localhost}" port: "${REDIS_PORT:6379}" - db: "${REDIS_DB:0}" - password: "${REDIS_PASSWORD:}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" # Check new version updates parameters updates: diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java index f1c4b5a8fc..4d56b4cb01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java @@ -31,35 +31,54 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.Assert; import org.thingsboard.server.common.data.id.EntityId; +import redis.clients.jedis.JedisPoolConfig; @Configuration @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false) @EnableCaching @Data -public class TBRedisCacheConfiguration { +public abstract class TBRedisCacheConfiguration { - @Value("${redis.connection.host}") - private String host; + @Value("${redis.pool_config.maxTotal}") + private int maxTotal; - @Value("${redis.connection.port}") - private Integer port; + @Value("${redis.pool_config.maxIdle}") + private int maxIdle; - @Value("${redis.connection.db}") - private Integer db; + @Value("${redis.pool_config.minIdle}") + private int minIdle; - @Value("${redis.connection.password}") - private String password; + @Value("${redis.pool_config.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${redis.pool_config.testOnReturn}") + private boolean testOnReturn; + + @Value("${redis.pool_config.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${redis.pool_config.minEvictableMs}") + private long minEvictableMs; + + @Value("${redis.pool_config.evictionRunsMs}") + private long evictionRunsMs; + + @Value("${redis.pool_config.maxWaitMills}") + private long maxWaitMills; + + @Value("${redis.pool_config.numberTestsPerEvictionRun}") + private int numberTestsPerEvictionRun; + + @Value("${redis.pool_config.blockWhenExhausted}") + private boolean blockWhenExhausted; @Bean public RedisConnectionFactory redisConnectionFactory() { - JedisConnectionFactory factory = new JedisConnectionFactory(); - factory.setHostName(host); - factory.setPort(port); - factory.setDatabase(db); - factory.setPassword(password); - return factory; + return loadFactory(); } + protected abstract JedisConnectionFactory loadFactory(); + @Bean public CacheManager cacheManager(RedisConnectionFactory cf) { DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService(); @@ -78,4 +97,20 @@ public class TBRedisCacheConfiguration { Assert.notNull(registry, "ConverterRegistry must not be null!"); registry.addConverter(EntityId.class, String.class, EntityId::toString); } + + protected JedisPoolConfig buildPoolConfig() { + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(maxTotal); + poolConfig.setMaxIdle(maxIdle); + poolConfig.setMinIdle(minIdle); + poolConfig.setTestOnBorrow(testOnBorrow); + poolConfig.setTestOnReturn(testOnReturn); + poolConfig.setTestWhileIdle(testWhileIdle); + poolConfig.setMinEvictableIdleTimeMillis(minEvictableMs); + poolConfig.setTimeBetweenEvictionRunsMillis(evictionRunsMs); + poolConfig.setMaxWaitMillis(maxWaitMills); + poolConfig.setNumTestsPerEvictionRun(numberTestsPerEvictionRun); + poolConfig.setBlockWhenExhausted(blockWhenExhausted); + return poolConfig; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java new file mode 100644 index 0000000000..8405db3c7d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisClusterConfiguration.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.cache; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Configuration +@ConditionalOnMissingBean(CaffeineCacheConfiguration.class) +@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "cluster") +public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration { + + private static final String COMMA = ","; + private static final String COLON = ":"; + + @Value("${redis.cluster.nodes}") + private String clusterNodes; + + @Value("${redis.cluster.max-redirects}") + private Integer maxRedirects; + + @Value("${redis.cluster.useDefaultPoolConfig}") + private boolean useDefaultPoolConfig; + + @Value("${redis.password}") + private String password; + + public JedisConnectionFactory loadFactory() { + RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); + clusterConfiguration.setClusterNodes(getNodes(clusterNodes)); + clusterConfiguration.setMaxRedirects(maxRedirects); + clusterConfiguration.setPassword(password); + if (useDefaultPoolConfig) { + return new JedisConnectionFactory(clusterConfiguration); + } else { + return new JedisConnectionFactory(clusterConfiguration, buildPoolConfig()); + } + } + + private List getNodes(String nodes) { + List result; + if (StringUtils.isBlank(nodes)) { + result = Collections.emptyList(); + } else { + result = new ArrayList<>(); + for (String hostPort : nodes.split(COMMA)) { + String host = hostPort.split(COLON)[0]; + Integer port = Integer.valueOf(hostPort.split(COLON)[1]); + result.add(new RedisNode(host, port)); + } + } + return result; + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java new file mode 100644 index 0000000000..b09b0dd565 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisStandaloneConfiguration.java @@ -0,0 +1,88 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.cache; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; + +import java.time.Duration; + +@Configuration +@ConditionalOnMissingBean(CaffeineCacheConfiguration.class) +@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "standalone") +public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { + + @Value("${redis.standalone.host}") + private String host; + + @Value("${redis.standalone.port}") + private Integer port; + + @Value("${redis.standalone.clientName}") + private String clientName; + + @Value("${redis.standalone.connectTimeout}") + private Long connectTimeout; + + @Value("${redis.standalone.readTimeout}") + private Long readTimeout; + + @Value("${redis.standalone.useDefaultClientConfig}") + private boolean useDefaultClientConfig; + + @Value("${redis.standalone.usePoolConfig}") + private boolean usePoolConfig; + + @Value("${redis.db}") + private Integer db; + + @Value("${redis.password}") + private String password; + + public JedisConnectionFactory loadFactory() { + RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(); + standaloneConfiguration.setHostName(host); + standaloneConfiguration.setPort(port); + standaloneConfiguration.setDatabase(db); + standaloneConfiguration.setPassword(password); + if (useDefaultClientConfig) { + return new JedisConnectionFactory(standaloneConfiguration); + } else { + return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); + } + } + + private JedisClientConfiguration buildClientConfig() { + if (usePoolConfig) { + return JedisClientConfiguration.builder() + .clientName(clientName) + .connectTimeout(Duration.ofMillis(connectTimeout)) + .readTimeout(Duration.ofMillis(readTimeout)) + .usePooling().poolConfig(buildPoolConfig()) + .build(); + } else { + return JedisClientConfiguration.builder() + .clientName(clientName) + .connectTimeout(Duration.ofMillis(connectTimeout)) + .readTimeout(Duration.ofMillis(readTimeout)).build(); + } + } +} \ No newline at end of file From 41527ca059f7d8bbae1e2a5b4b97edcce09246e0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Oct 2019 12:44:52 +0300 Subject: [PATCH 026/120] Version set to 2.4.2-SNAPSHOT. Restore Black Box Tests. --- .travis.yml | 2 +- application/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- dao/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/package-lock.json | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/web-ui/package-lock.json | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 +-- pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- ui/package-lock.json | 30 ++++++++++++++++------ ui/package.json | 2 +- ui/pom.xml | 2 +- 41 files changed, 63 insertions(+), 49 deletions(-) diff --git a/.travis.yml b/.travis.yml index f635da5a55..94ce1b76e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ language: java sudo: required services: - docker -script: mvn clean verify -Ddockerfile.skip=false +script: mvn clean verify -Ddockerfile.skip=false -DblackBoxTests.skip=false -DblackBoxTests.skipTailChildContainers=true diff --git a/application/pom.xml b/application/pom.xml index 24c3b6a950..68ae1aa77c 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard application diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 05cc589eb2..b2a8cd467e 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index be1b6393fb..155a00b7f9 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 10b0ec7494..60702c0801 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 7492cb3199..ce20b753ba 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 7f09972f52..7821051137 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 604c97b0fb..26d478b596 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 0757263353..644df5a7b4 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 936416229a..ef0ae8eabd 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index dc00a7f55a..ca36845902 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 826d00787d..88bc2f64d8 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index d6c1d8afd3..c695ac573e 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index be764b97eb..ba44133fb8 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard dao diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index e36ef1fd42..a6b5d65ff7 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index c61ef011a2..52ea28f0c7 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard-js-executor", - "version": "2.4.1", + "version": "2.4.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index d61162dd5b..dbbfe6aea1 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "2.4.1", + "version": "2.4.2", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 278e005c10..27636dbecd 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index 004109f926..84d00e2d88 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 1028283c50..8a2bab084a 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 6ccc50639a..6be069687f 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index f948de66b3..b606ba29fd 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 7b148ae9d2..c38756d5cd 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index f29c0e9ee0..7402d2e209 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 5bf9fbe7e1..f869e02f18 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package-lock.json b/msa/web-ui/package-lock.json index 9298d31f27..3ceb92e809 100644 --- a/msa/web-ui/package-lock.json +++ b/msa/web-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard-web-ui", - "version": "2.4.1", + "version": "2.4.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 2d36752300..ce82272a26 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "2.4.1", + "version": "2.4.2", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 801d3e6727..c7bcf39caf 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index c7eb97b0dd..869eb2abb0 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,12 +19,12 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard org.thingsboard netty-mqtt - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 6c3b298391..f0a0aae6cd 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT pom Thingsboard diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 1fe931962e..4197f275b6 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 8874c051ee..02e4d128dc 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 5c040a389f..627aa42ab3 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index 0d044365a0..b192c1bf38 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index f3960dce9b..08629c2a5e 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 01b5d0be5a..8722273815 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 0849406295..badb11f6cd 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 6d56741015..c830bbc009 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard transport diff --git a/ui/package-lock.json b/ui/package-lock.json index dab90f1904..ced5230b8c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "2.4.1", + "version": "2.4.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6096,12 +6096,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6116,17 +6118,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6243,7 +6248,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6255,6 +6261,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6269,6 +6276,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6276,12 +6284,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6300,6 +6310,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6380,7 +6391,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6392,6 +6404,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6513,6 +6526,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/ui/package.json b/ui/package.json index 466aa59618..bb7c1b4979 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard", "private": true, - "version": "2.4.1", + "version": "2.4.2", "description": "ThingsBoard UI", "licenses": [ { diff --git a/ui/pom.xml b/ui/pom.xml index 27caa78236..f037ccfde6 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 2.4.2-SNAPSHOT thingsboard org.thingsboard From f472855019ac8a3361f227c8bdca748074a550bb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Oct 2019 14:01:17 +0300 Subject: [PATCH 027/120] Revert travis config. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94ce1b76e0..f635da5a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ language: java sudo: required services: - docker -script: mvn clean verify -Ddockerfile.skip=false -DblackBoxTests.skip=false -DblackBoxTests.skipTailChildContainers=true +script: mvn clean verify -Ddockerfile.skip=false From d344f1d08f4b000f3b9ccecba73c4535eebe8068 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Oct 2019 15:47:05 +0300 Subject: [PATCH 028/120] Minor refactoring. --- .../thingsboard/server/install/ThingsboardInstallService.java | 1 + .../org/thingsboard/server/dao/device/ClaimDevicesService.java | 0 .../java/org/thingsboard/server/dao/device/claim/ClaimData.java | 0 .../org/thingsboard/server/dao/device/claim/ClaimResponse.java | 0 .../org/thingsboard/server/dao/device/claim/ClaimResult.java | 0 5 files changed, 1 insertion(+) rename {dao => common/dao-api}/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java (100%) rename {dao => common/dao-api}/src/main/java/org/thingsboard/server/dao/device/claim/ClaimData.java (100%) rename {dao => common/dao-api}/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResponse.java (100%) rename {dao => common/dao-api}/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResult.java (100%) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 99f31323f2..56c90a2dab 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -138,6 +138,7 @@ public class ThingsboardInstallService { systemDataLoaderService.deleteSystemWidgetBundle("gateway_widgets"); systemDataLoaderService.deleteSystemWidgetBundle("input_widgets"); systemDataLoaderService.deleteSystemWidgetBundle("date"); + systemDataLoaderService.deleteSystemWidgetBundle("entity_admin_widgets"); systemDataLoaderService.loadSystemWidgets(); break; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java similarity index 100% rename from dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimData.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimData.java similarity index 100% rename from dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimData.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimData.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResponse.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResponse.java similarity index 100% rename from dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResponse.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResponse.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResult.java similarity index 100% rename from dao/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResult.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ClaimResult.java From a2c83820c3212097098ec8f9693f07f92e4ea9f9 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 23 Oct 2019 11:09:32 +0300 Subject: [PATCH 029/120] Change background color select dashboard (#2115) --- ui/src/app/dashboard/dashboard.tpl.html | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html index f27be66e5f..5a77d95253 100644 --- a/ui/src/app/dashboard/dashboard.tpl.html +++ b/ui/src/app/dashboard/dashboard.tpl.html @@ -74,7 +74,6 @@ settings From 18041c34c74f735554eb4f96043c9f4f13475ea7 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Wed, 23 Oct 2019 17:58:46 +0300 Subject: [PATCH 030/120] rest api call node added redis queue support (#2112) * rest api call node added redis queue support * rest api node refactoring --- .../server/actors/ActorSystemContext.java | 5 + .../actors/ruleChain/DefaultTbContext.java | 12 +- .../thingsboard/server/common/msg/TbMsg.java | 18 ++- .../dao/cache/TBRedisCacheConfiguration.java | 8 + rule-engine/rule-engine-api/pom.xml | 5 + .../rule/engine/api/TbContext.java | 7 +- .../rule/engine/rest/TbHttpClient.java | 153 ++++++++++++++++++ .../engine/rest/TbRedisQueueProcessor.java | 125 ++++++++++++++ .../rule/engine/rest/TbRestApiCallNode.java | 126 +++------------ .../rest/TbRestApiCallNodeConfiguration.java | 5 + 10 files changed, 354 insertions(+), 110 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRedisQueueProcessor.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 42b66a73e8..eecf979a70 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleChainTransactionService; @@ -336,6 +337,10 @@ public class ActorSystemContext { @Getter private CassandraBufferedRateExecutor cassandraBufferedRateExecutor; + @Autowired(required = false) + @Getter + private RedisTemplate redisTemplate; + public ActorSystemContext() { config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 5cfc2f9a19..669257dcfd 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; @@ -60,7 +61,6 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -361,6 +361,16 @@ class DefaultTbContext implements TbContext { return mainCtx.getCassandraBufferedRateExecutor().submit(task); } + @Override + public RedisTemplate getRedisTemplate() { + return mainCtx.getRedisTemplate(); + } + + @Override + public String getServerAddress() { + return mainCtx.getServerAddress(); + } + private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("ruleNodeId", ruleNodeId.toString()); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index be346db040..cbc9b979bf 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -67,7 +67,7 @@ public final class TbMsg implements Serializable { this(id, type, originator, metaData, dataType, data, new TbMsgTransactionData(id, originator), ruleChainId, ruleNodeId, clusterPartition); } - public static ByteBuffer toBytes(TbMsg msg) { + public static byte[] toByteArray(TbMsg msg) { MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); builder.setId(msg.getId().toString()); builder.setType(msg.getType()); @@ -101,8 +101,16 @@ public final class TbMsg implements Serializable { builder.setDataType(msg.getDataType().ordinal()); builder.setData(msg.getData()); - byte[] bytes = builder.build().toByteArray(); - return ByteBuffer.wrap(bytes); + return builder.build().toByteArray(); + + } + + public static ByteBuffer toBytes(TbMsg msg) { + return ByteBuffer.wrap(toByteArray(msg)); + } + + public static TbMsg fromBytes(byte[] data) { + return fromBytes(ByteBuffer.wrap(data)); } public static TbMsg fromBytes(ByteBuffer buffer) { @@ -115,8 +123,8 @@ public final class TbMsg implements Serializable { EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB())); RuleNodeId ruleNodeId = null; - if(proto.getRuleNodeIdMSB() != 0L && proto.getRuleNodeIdLSB() != 0L) { - ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); + if (proto.getRuleNodeIdMSB() != 0L && proto.getRuleNodeIdLSB() != 0L) { + ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); } TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), transactionData, ruleChainId, ruleNodeId, proto.getClusterPartition()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java index 4d56b4cb01..5ed0222d70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.Assert; import org.thingsboard.server.common.data.id.EntityId; @@ -93,6 +94,13 @@ public abstract class TBRedisCacheConfiguration { return new PreviousDeviceCredentialsIdKeyGenerator(); } + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + return template; + } + private static void registerDefaultConverters(ConverterRegistry registry) { Assert.notNull(registry, "ConverterRegistry must not be null!"); registry.addConverter(EntityId.class, String.class, EntityId::toString); diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 02e4d128dc..444b88cc8c 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -83,5 +83,10 @@ cassandra-driver-extras provided + + org.springframework.data + spring-data-redis + provided + \ No newline at end of file diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 034aec3000..122715b2d4 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -15,7 +15,9 @@ */ package org.thingsboard.rule.engine.api; +import com.datastax.driver.core.ResultSetFuture; import io.netty.channel.EventLoopGroup; +import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; @@ -41,8 +43,6 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; -import com.datastax.driver.core.ResultSetFuture; - import java.util.Set; /** @@ -132,4 +132,7 @@ public interface TbContext { ResultSetFuture submitCassandraTask(CassandraStatementTask task); + RedisTemplate getRedisTemplate(); + + String getServerAddress(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java new file mode 100644 index 0000000000..a3e119f588 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -0,0 +1,153 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.rest; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.ssl.SslContextBuilder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.web.client.HttpClientErrorException; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.net.ssl.SSLException; +import java.util.concurrent.TimeUnit; + +@Data +@Slf4j +class TbHttpClient { + + private static final String STATUS = "status"; + private static final String STATUS_CODE = "statusCode"; + private static final String STATUS_REASON = "statusReason"; + private static final String ERROR = "error"; + private static final String ERROR_BODY = "error_body"; + + private final TbRestApiCallNodeConfiguration config; + private final boolean useRedisQueueForMsgPersistence; + + private EventLoopGroup eventLoopGroup; + private AsyncRestTemplate httpClient; + + TbHttpClient(TbRestApiCallNodeConfiguration config) throws TbNodeException { + try { + this.config = config; + this.useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence(); + if (config.isUseSimpleClientHttpFactory()) { + httpClient = new AsyncRestTemplate(); + } else { + this.eventLoopGroup = new NioEventLoopGroup(); + Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); + nettyFactory.setSslContext(SslContextBuilder.forClient().build()); + httpClient = new AsyncRestTemplate(nettyFactory); + } + } catch (SSLException e) { + throw new TbNodeException(e); + } + } + + void destroy() { + if (this.eventLoopGroup != null) { + this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); + } + } + + void processMessage(TbContext ctx, TbMsg msg, TbRedisQueueProcessor queueProcessor) { + String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData()); + HttpHeaders headers = prepareHeaders(msg.getMetaData()); + HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); + HttpEntity entity = new HttpEntity<>(msg.getData(), headers); + + ListenableFuture> future = httpClient.exchange( + endpointUrl, method, entity, String.class); + future.addCallback(new ListenableFutureCallback>() { + @Override + public void onFailure(Throwable throwable) { + if (useRedisQueueForMsgPersistence) { + queueProcessor.pushOnFailure(msg); + } + TbMsg next = processException(ctx, msg, throwable); + ctx.tellFailure(next, throwable); + } + + @Override + public void onSuccess(ResponseEntity responseEntity) { + if (responseEntity.getStatusCode().is2xxSuccessful()) { + if (useRedisQueueForMsgPersistence) { + queueProcessor.resetCounter(); + } + TbMsg next = processResponse(ctx, msg, responseEntity); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + if (useRedisQueueForMsgPersistence) { + queueProcessor.pushOnFailure(msg); + } + TbMsg next = processFailureResponse(ctx, msg, responseEntity); + ctx.tellNext(next, TbRelationTypes.FAILURE); + } + } + }); + } + + private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + TbMsgMetaData metaData = origMsg.getMetaData(); + metaData.putValue(STATUS, response.getStatusCode().name()); + metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); + metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + response.getHeaders().toSingleValueMap().forEach(metaData::putValue); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); + } + + private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + TbMsgMetaData metaData = origMsg.getMetaData(); + metaData.putValue(STATUS, response.getStatusCode().name()); + metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); + metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + metaData.putValue(ERROR_BODY, response.getBody()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + TbMsgMetaData metaData = origMsg.getMetaData(); + metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); + if (e instanceof HttpClientErrorException) { + HttpClientErrorException httpClientErrorException = (HttpClientErrorException) e; + metaData.putValue(STATUS, httpClientErrorException.getStatusText()); + metaData.putValue(STATUS_CODE, httpClientErrorException.getRawStatusCode() + ""); + metaData.putValue(ERROR_BODY, httpClientErrorException.getResponseBodyAsString()); + } + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { + HttpHeaders headers = new HttpHeaders(); + config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData))); + return headers; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRedisQueueProcessor.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRedisQueueProcessor.java new file mode 100644 index 0000000000..7e10b469f2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRedisQueueProcessor.java @@ -0,0 +1,125 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.rest; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.ListOperations; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Data +@Slf4j +class TbRedisQueueProcessor { + + private static final int MAX_QUEUE_SIZE = Integer.MAX_VALUE; + + private final TbContext ctx; + private final TbHttpClient httpClient; + private final ExecutorService executor; + private final ListOperations listOperations; + private final String redisKey; + private final boolean trimQueue; + private final int maxQueueSize; + + private AtomicInteger failuresCounter; + private Future future; + + TbRedisQueueProcessor(TbContext ctx, TbHttpClient httpClient, boolean trimQueue, int maxQueueSize) { + this.ctx = ctx; + this.httpClient = httpClient; + this.executor = Executors.newSingleThreadExecutor(); + this.listOperations = ctx.getRedisTemplate().opsForList(); + this.redisKey = constructRedisKey(); + this.trimQueue = trimQueue; + this.maxQueueSize = maxQueueSize; + init(); + } + + private void init() { + failuresCounter = new AtomicInteger(0); + future = executor.submit(() -> { + while (true) { + if (failuresCounter.get() != 0 && failuresCounter.get() % 50 == 0) { + sleep("Target HTTP server is down...", 3); + } + if (listOperations.size(redisKey) > 0) { + List list = listOperations.range(redisKey, -10, -1); + list.forEach(obj -> { + TbMsg msg = TbMsg.fromBytes((byte[]) obj); + log.debug("Trying to send the message: {}", msg); + listOperations.remove(redisKey, -1, obj); + httpClient.processMessage(ctx, msg, this); + }); + } else { + sleep("Queue is empty, waiting for tasks!", 1); + } + } + }); + } + + void destroy() { + if (future != null) { + future.cancel(true); + } + if (executor != null) { + executor.shutdownNow(); + } + } + + void push(TbMsg msg) { + listOperations.leftPush(redisKey, TbMsg.toByteArray(msg)); + if (trimQueue) { + listOperations.trim(redisKey, 0, validateMaxQueueSize()); + } + } + + void pushOnFailure(TbMsg msg) { + listOperations.rightPush(redisKey, TbMsg.toByteArray(msg)); + failuresCounter.incrementAndGet(); + } + + void resetCounter() { + failuresCounter.set(0); + } + + private String constructRedisKey() { + return ctx.getServerAddress() + ctx.getSelfId(); + } + + private int validateMaxQueueSize() { + if (maxQueueSize != 0) { + return maxQueueSize; + } + return MAX_QUEUE_SIZE; + } + + private void sleep(String logMessage, int sleepSeconds) { + try { + log.debug(logMessage); + TimeUnit.SECONDS.sleep(sleepSeconds); + } catch (InterruptedException e) { + throw new IllegalStateException("Thread interrupted!", e); + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index e290c18d14..9fa65e775c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -15,28 +15,17 @@ */ package org.thingsboard.rule.engine.rest; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.handler.ssl.SslContextBuilder; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.Netty4ClientHttpRequestFactory; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.web.client.AsyncRestTemplate; -import org.springframework.web.client.HttpClientErrorException; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import javax.net.ssl.SSLException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; @Slf4j @RuleNode( @@ -56,107 +45,40 @@ import java.util.concurrent.TimeUnit; ) public class TbRestApiCallNode implements TbNode { - private static final String STATUS = "status"; - private static final String STATUS_CODE = "statusCode"; - private static final String STATUS_REASON = "statusReason"; - private static final String ERROR = "error"; - private static final String ERROR_BODY = "error_body"; - - private TbRestApiCallNodeConfiguration config; - - private EventLoopGroup eventLoopGroup; - private AsyncRestTemplate httpClient; + private boolean useRedisQueueForMsgPersistence; + private TbHttpClient httpClient; + private TbRedisQueueProcessor queueProcessor; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - try { - this.config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); - if (this.config.isUseSimpleClientHttpFactory()) { - httpClient = new AsyncRestTemplate(); - } else { - this.eventLoopGroup = new NioEventLoopGroup(); - Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); - nettyFactory.setSslContext(SslContextBuilder.forClient().build()); - httpClient = new AsyncRestTemplate(nettyFactory); + TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); + httpClient = new TbHttpClient(config); + useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence(); + if (useRedisQueueForMsgPersistence) { + if (ctx.getRedisTemplate() == null) { + throw new RuntimeException("Redis cache type must be used!"); } - } catch (SSLException e) { - throw new TbNodeException(e); + queueProcessor = new TbRedisQueueProcessor(ctx, httpClient, config.isTrimQueue(), config.getMaxQueueSize()); } } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData()); - HttpHeaders headers = prepareHeaders(msg.getMetaData()); - HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); - HttpEntity entity = new HttpEntity<>(msg.getData(), headers); - - ListenableFuture> future = httpClient.exchange( - endpointUrl, method, entity, String.class); - - future.addCallback(new ListenableFutureCallback>() { - @Override - public void onFailure(Throwable throwable) { - TbMsg next = processException(ctx, msg, throwable); - ctx.tellFailure(next, throwable); - } - - @Override - public void onSuccess(ResponseEntity responseEntity) { - if (responseEntity.getStatusCode().is2xxSuccessful()) { - TbMsg next = processResponse(ctx, msg, responseEntity); - ctx.tellNext(next, TbRelationTypes.SUCCESS); - } else { - TbMsg next = processFailureResponse(ctx, msg, responseEntity); - ctx.tellNext(next, TbRelationTypes.FAILURE); - } - } - }); + if (useRedisQueueForMsgPersistence) { + queueProcessor.push(msg); + } else { + httpClient.processMessage(ctx, msg, null); + } } @Override public void destroy() { - if (this.eventLoopGroup != null) { - this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); + if (this.httpClient != null) { + this.httpClient.destroy(); } - } - - private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { - TbMsgMetaData metaData = origMsg.getMetaData(); - metaData.putValue(STATUS, response.getStatusCode().name()); - metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); - metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); - response.getHeaders().toSingleValueMap().forEach(metaData::putValue); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); - } - - private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { - TbMsgMetaData metaData = origMsg.getMetaData(); - metaData.putValue(STATUS, response.getStatusCode().name()); - metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); - metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); - metaData.putValue(ERROR_BODY, response.getBody()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); - } - - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { - TbMsgMetaData metaData = origMsg.getMetaData(); - metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - if (e instanceof HttpClientErrorException) { - HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e; - metaData.putValue(STATUS, httpClientErrorException.getStatusText()); - metaData.putValue(STATUS_CODE, httpClientErrorException.getRawStatusCode()+""); - metaData.putValue(ERROR_BODY, httpClientErrorException.getResponseBodyAsString()); + if (this.queueProcessor != null) { + this.queueProcessor.destroy(); } - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); - } - - private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { - HttpHeaders headers = new HttpHeaders(); - config.getHeaders().forEach((k,v) -> { - headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData)); - }); - return headers; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index 6fa3f7151e..d69e594513 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -28,6 +28,9 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration headers; private boolean useSimpleClientHttpFactory; + private boolean useRedisQueueForMsgPersistence; + private boolean trimQueue; + private int maxQueueSize; @Override public TbRestApiCallNodeConfiguration defaultConfiguration() { @@ -36,6 +39,8 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration Date: Tue, 22 Oct 2019 16:46:45 +0300 Subject: [PATCH 031/120] rest api call node ui fix --- .../public/static/rulenode/rulenode-core-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 0f252135e2..a9f344d85e 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,6 +1,6 @@ !function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; -},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
'; -},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),$=i(q),x=n(60),k=i(x),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),_=i(N),S=n(62),E=i(S),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),J=n(65),W=i(J);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",W.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly" -},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),$=i(q),x=n(103),k=i(x);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.", -"start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'; +},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),$=n(60),k=i($),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),J=n(65),W=i(J);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",W.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}} +a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),$=n(103),k=i($);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.", +"delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file From a7bd77e91f7c296e83708e59f97a32d3a1ac07a0 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 29 Oct 2019 17:09:50 +0200 Subject: [PATCH 032/120] Updated PostgreSQL driver version to 9.4.1212 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f0a0aae6cd..80038123ff 100755 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 2.5.0 2.5.3 1.2.1 - 9.4.1211 + 9.4.1212 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* From 3ad552b735d094f9813e50475094bbba784dab6d Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 30 Oct 2019 18:43:07 +0200 Subject: [PATCH 033/120] Allow Nulls in JsonConverter for usability --- .../server/common/transport/adaptor/JsonConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 24b1343491..af757c2c27 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -201,10 +201,10 @@ public class JsonConverter { .setBoolV(value.getAsBoolean()).build()); } else if (value.isNumber()) { result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } else { + } else if (!value.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } - } else { + } else if (!element.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } } From 46c7862258e0cea1b39a6a462914d4d6d634bdae Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 6 Nov 2019 17:14:33 +0200 Subject: [PATCH 034/120] Activation Link(added logic if x-forwarded-proto is set but x-forwarded-port not set, set port to 80 or 443) --- .../server/controller/BaseController.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 3fc809c424..77a9d3ad0f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -516,15 +516,27 @@ public abstract class BaseController { protected String constructBaseUrl(HttpServletRequest request) { String scheme = request.getScheme(); - if (request.getHeader("x-forwarded-proto") != null) { - scheme = request.getHeader("x-forwarded-proto"); + + String forwardedProto = request.getHeader("x-forwarded-proto"); + if (forwardedProto != null) { + scheme = forwardedProto; } + int serverPort = request.getServerPort(); if (request.getHeader("x-forwarded-port") != null) { try { serverPort = request.getIntHeader("x-forwarded-port"); } catch (NumberFormatException e) { } + } else if (forwardedProto != null) { + switch (forwardedProto) { + case "http": + serverPort = 80; + break; + case "https": + serverPort = 443; + break; + } } String baseUrl = String.format("%s://%s:%d", From ac8e67eff09a28d68a4c3733b5dc454988684d21 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Fri, 8 Nov 2019 14:19:15 +0200 Subject: [PATCH 035/120] Create new input widgets for edit location enity to map (#2138) --- .../system/widget_bundles/input_widgets.json | 48 ++++++ ui/src/app/widget/lib/add-entity-panel.scss | 27 ++++ .../app/widget/lib/add-entity-panel.tpl.html | 22 +++ ui/src/app/widget/lib/google-map.js | 16 +- ui/src/app/widget/lib/image-map.js | 30 +++- ui/src/app/widget/lib/map-widget2.js | 146 +++++++++++++++++- ui/src/app/widget/lib/openstreet-map.js | 16 +- ui/src/app/widget/lib/tencent-map.js | 15 +- 8 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 ui/src/app/widget/lib/add-entity-panel.scss create mode 100644 ui/src/app/widget/lib/add-entity-panel.tpl.html diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 47e25203e4..51717b12ec 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -356,6 +356,54 @@ "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"deviceSecret\":true,\"showLabel\":true},\"title\":\"Device claiming widget\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"enableDataExport\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } + }, + { + "alias": "markers_placement_image_map", + "name": "Markers Placement - Image Map", + "descriptor": { + "type": "latest", + "sizeX": 8.5, + "sizeY": 6.5, + "resources": [], + "templateHtml": "", + "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, null, null, true);\n var createEntityLocation = {\n name: 'action.add',\n show: true,\n onAction: function($event) {\n self.ctx.map.selectEntity($event);\n },\n icon: 'add_location'\n };\n self.ctx.widgetActions = [createEntityLocation];\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('image-map');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('image-map');\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}

Delete\",\"markerImageSize\":34,\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"color\":\"#fe7569\",\"mapImageUrl\":\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMTEzNC41MTgzIgogICBoZWlnaHQ9Ijc2Mi43ODI0MSIKICAgaWQ9InN2ZzIiCiAgIHZlcnNpb249IjEuMSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC40OC41IHIxMDA0MCIKICAgc29kaXBvZGk6ZG9jbmFtZT0id2ljaGl0YW1hcC1ub2xpYi5zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0IiAvPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSIwLjM1IgogICAgIGlua3NjYXBlOmN4PSI4OS45MDc4NTciCiAgICAgaW5rc2NhcGU6Y3k9IjQ1My43ODI0MSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzNjYiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iNzIxIgogICAgIGlua3NjYXBlOndpbmRvdy14PSItNCIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpvYmplY3QtcGF0aHM9InRydWUiCiAgICAgaW5rc2NhcGU6c25hcC1nbG9iYWw9ImZhbHNlIgogICAgIHNob3dndWlkZXM9InRydWUiCiAgICAgaW5rc2NhcGU6Z3VpZGUtYmJveD0idHJ1ZSIKICAgICBmaXQtbWFyZ2luLXRvcD0iMCIKICAgICBmaXQtbWFyZ2luLWxlZnQ9IjAiCiAgICAgZml0LW1hcmdpbi1yaWdodD0iMCIKICAgICBmaXQtbWFyZ2luLWJvdHRvbT0iMCIgLz4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE3Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjcuMDcxNDI4LC0zMDcuOTAyOTkpIj4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDM3ODciCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzY0ZTU5O3N0cm9rZS13aWR0aDoyLjk5OTk5OTc2O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmUiCiAgICAgICBkPSJtIDkwNi4wMzMxNSw3MDYuMTMzNjcgMy40MjkyLDE3Ljc5NTUyIE0gMjguNTcxNDI4LDc2NS4wNTA2NyBjIDE1MC40MzUyMDIsNi44MzM0MiAxNDYuMzkyMzIyLC0yNi4zMzQxNSAxNjYuNDM0NTQyLC0yOS4zMjAwOSAzNi4xNDM3NSwtNS4zODQ3NiAxMTQuMjg2NzYsLTYuNTI1NCAxNDguMzI1MDgsLTguNjIzNTQgNDMuMzc4MDgsLTIuNjczODUgMTQxLjc2MjIxLC0xMS4yMzA5OSAxODguODU1NzgsLTE5LjgzNDE4IDM5LjgxMTM4LC03LjI3Mjg0IDIyMS4zNjk5MSwtMC44NjIzNSAzMTkuMDcxNDEsLTAuODYyMzUgNzAuODI3MzUsMCAxNDYuOTE4NjcsLTEuNzI0NyAyMTguMTc1ODYsLTEuNzI0NyAtMzEuNjE5NywwIDExNy44NTUyLC0yLjU4NzA3IDg2LjIzNTUsLTIuNTg3MDcgbSAtMjUuMDkwNywtNjguMTI2MDYgYyAtNTIuNzk5NiwzNC43ODQ4NCAtNjUuODk1MSw1MS43NDg2NSAtOTUuNjM5LDgxLjQ5MjU4IC0yNC45MzEzLDI0LjkzMTI3IC0xNDAuMzk2NTMsLTE5LjEzOTIgLTE3OC45Mzg3MSwzNi42NTAwNyAtMTIuMjgxNCwxNy43NzcxNSAtNDcuMDAyNTcsNDYuNTQ2NTMgLTY1LjEwNzgzLDU5LjA3MTMzIC0yMC4xMDUsMTMuOTA4MTggLTU2LjAzNjcyLDQ0Ljk1NjY0IC02Ny43Njg4NSw3My4wNzgyNyAtNC44MDE0NywxMS41MDkwMiAtMTMuMzgwNDYsMzUuOTkyOTggLTIzLjQ0OTQ5LDQ2LjA2MjAxIC0xMC40OTY5OSwxMC40OTY5OSAtMzguMzc3MzMsNi4zODU2OSAtNDQuMDIzNDUsMTcuNjQ3NjQgLTE5LjAwNTAyLDM3LjkwODEyIC0yNS40NjUzLDEwMC45MjM1MiAtNjcuNjE3ODksMTAyLjA1MTAyIG0gMTkuMjgxNTEsLTYyNC4wMTQ2NCBjIDM0LjY1OTM0LC0xLjg3MzgyIDg0LjAyNzMzLDcuMzkxMzEgMTA5LjkwMDcxLC00LjI4NTQ1IDEzLjI4MTcyLC01Ljk5NDA4IDQxLjQwNzIxLC0yLjQ2MTM1IDY2LjgyODY2LC0yLjMyMDQ2IDM1LjMyMjM4LDAuMTk1NzggNjQuMzgyNDksMC42MzQ3NyAxMDEuOTE2Nyw1LjAyMzIgMjUuMDMwMzYsMi45MjY1IDQ0LjY2MjczLDM0LjI4NzIyIDU4LjUyNjk4LDUwLjY0MzkgMTcuMDk4NzgsMjAuMTcyNjggNjIuNzYzODYsLTEuNzE0NjcgNjYuMzA1NjYsMzIuMTM0MzMgNS4xMDI3LDQ4Ljc2NTg3IC02LjMyODQsNzguNjM3MjUgNi4xNDExLDk3LjM0MTUgMTkuOTY5MiwyOS45NTM3OSA1MC40ODY0LDE3Ljg1NTc5IDQ0LjYxOTMsODMuOTcxMTkgTSA1ODkuMTAyMjcsMzA5LjcyNzE1IGMgNC42NDM0NiwyMy43MjkyMyAxNS4wNjkwNCw3Mi43NzU3NSAxOS4wNjEyOCwxMzAuNjQyODggMC44NzIwNiwxMi42NDA0OCA1LjQ0NzE4LDI0Ljk5MjUzIDQuMjIyMzEsNDUuMjc3NTcgLTIuNTE3MjEsNDEuNjg3NSAtMTUuNzE3MDYsNDMuNjc3MjcgLTE1LjA5MTIyLDYwLjM2NDg2IDEuNDMxOTUsMzguMTgyMjQgMzAuNjEzNjEsOTMuODM3MTkgMzAuNjEzNjEsMTM5LjcwMTU0IDAsMjQuMTgwOCAtMi42Njk2NCwxMTUuMzkwNDUgNy4zMzAwMSwxMzUuMzg5NzYgMC4xNTkxMSwwLjMxODIxIDEwLjA2NDc2LDM1Ljg4MzMyIDEwLjc3OTQ1LDQ5LjE1NDI0IDAuOTQzNzgsMTcuNTI0NjkgLTI0LjQ3OCwzOS40NzAwOCAtMjguMDI2NTUsNDYuNTY3MTYgLTUuNDc3NywxMC45NTUzOSAtMzYuOTczMjQsMTAuODgxOTcgLTQwLjA5OTUsMjQuMTQ1OTUgLTMuODY4ODQsMTYuNDE0NTEgLTMuODY2Myw0My43OTczNSA0LjA0NjQ3LDU5LjQ0MTI5IG0gOTcuMzM3MzQsLTY5MS4wMDk0MSBjIC01LjAxMzMyLDM1LjUxNTk1IC00My42NTkwMSwxMS4zMTY1MiAtNTguNTM4NjEsMjMuNzgxMzEgLTIxLjMzMDE5LDE3Ljg2ODUyIC02Mi40OTk2NCwzMS40MzIxMiAtNzAuMTI0MzcsMzUuMzY3MDggLTM1LjA4NzYzLDE4LjEwNzkzIC0xMTAuNDcyMTUsLTE1LjE0MTk2IC0xMjUuNjE0MSw0LjI2ODQzIC0xNS45NTA2MywyMC40NDcwMyAtMC4wNzM1LDYxLjQ2NjQ4IC05LjE0NjY2LDg0LjE0OTI0IC02LjAzNTcsMTUuMDg5MjYgLTE4Ljg3NjcsMjMuMDE3MzQgLTI3LjQzOTk3LDMyLjkyNzk4IC0xOS43NDgyOSwyMi44NTU1NSAtNjkuOTc0MjgsNjkuODI0MTkgLTg0Ljc1OTA0LDEwMC4wMDM0NiAtNy40OTc0MSwxNS4zMDQwNCAtMy4yODQyNiw0NC40MjA0MSAtMy40NzA1Myw2My4zNDI4NCAtMC4xMjc5MywxMi45OTQxNCAtMC44MTAxNSwyMy4xMDM4NSAyLjQwMzQzLDI4LjI3NjE4IDQuOTYxNTgsNy45ODU4MSAyMy43MjA1LDI4LjExMjA3IDI0LjIzODY1LDUwLjYxMTQ5IDAuMjk0MTEsMTIuNzcxNDYgMC4wMTMzLDc4LjU5MTAxIDMuMDQ4ODgsODcuNjU1NDkgMi4zMTI1Niw2LjkwNTQ2IDQuMjIwMDQsMjYuNTY0OTcgMTAuMjEzNzcsMzYuNTg2NjIgMTEuMzU0MDEsMTguOTg0MTUgNC4zODczNyw0MC4xNTY2MiAyNy44OTczLDUzLjUwNzk1IDE5LjA1MDEyLDEwLjgxODU5IDQ2Ljg3NzgxLDEyLjIxODYyIDgxLjkyNjE4LDE0LjQ2MDU0IDMzLjcwMzQ1LDIuMTU1ODkgNjEuNTEyMTcsLTEuNDMwMzUgNzYuOTIwNzcsNi4xNDExIDExLjU4NTA4LDUuNjkyNjYgOC41ODE1MSwxNy45MzM0NCAxNC4yOTU0MSwyOS4zNjEyMyA1LjY0MDQyLDExLjI4MDg1IDMxLjUwMjYzLDExLjE1NjI3IDQxLjgwNDA5LDQzLjQ1NDg3IDcuNjA1OSwyMy44NDcxIDMuMDg1OTMsNDQuMTU2OSA2LjcwNzU1LDY1Ljg4NjYiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2Nzc3NzY2Njc3Nzc3NzY2Nzc3Nzc3NjY3Nzc3Njc3NzY2Nzc3Nzc3Nzc3Nzc3Nzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNDMuMjc3ODgxLDUxNy45NDY3OSBjIDAsMCAyMzAuODQ4Mjg5LC0zLjYzODA1IDI1MC4wMDg2MzksLTMuNjU4NjcgNy40ODIyMiwtMC4wMDggOC42MTk1NCw1LjE1MTk0IDE0LjAyMDksMTEuNDU4NjkgMjQuNTk2MDgsMjguNzE4OTMgOTMuOTA5NjYsMTEyLjkzNTg1IDkzLjkwOTY2LDExMi45MzU4NSIKICAgICAgIGlkPSJwYXRoMzc4OSIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDM1Ljk2MDU1NSw1NzcuNzA0OTQgYyAwLDAgMTY1LjUyNDU2NSwtMS42ODQ1NCAyNDguNzc5NTY1LC0xLjY4NDU0IDQuOTQ3NDksMCA3LjcyOTkzLC0yLjg4MzMgMTAuNTM3NzEsLTUuNzI5NzcgOS42NjEwNywtOS43OTQxNiAyNS42MzE5OSwtMjguNTg5OTUgMjUuNjMxOTksLTI4LjU4OTk1IgogICAgICAgaWQ9InBhdGgzNzkxIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOiMzMzMzNjY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gMzguMzk5NjYzLDY0MS43MzE1NSA0MzEuNzA1OTMsNjM3LjQ2MzExIgogICAgICAgaWQ9InBhdGgzNzk1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOiMzMzMzNjY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gMzkuMDA5NDQyLDcwNC41Mzg1OSA1MjMuMTcyNTMsNjk3LjgzMTA0IgogICAgICAgaWQ9InBhdGgzNzk3IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzAzLjk1NzYyLDY4Mi41ODY2MSAxNDYuNzk1NDIsMS44MjkzMyBjIDEwLjUzNDAzLDAuMTMxMjcgMTQuMzQzNzQsLTIuNjM3MzkgMjUuNDg3MTUsLTYuMzcyOCAxMC40MTIxMiwtMy40OTAyNyAzMS40MjQxNSwtMi42OTg5NiA0MS4zODUzOCwtMi43NzM4NSBsIDQwNS41NjA3OSwtMy4wNDg5IgogICAgICAgaWQ9InBhdGgzNzk5IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgaWQ9InBhdGgzODA0IgogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDQyNi4yMTc5NCwzMTQuODkwOTggYyAyLjA2NzU0LDkuMDUyNzMgMS44NDE3Nyw1MS43Mjc3NyA2LjUwNzk0LDc0LjgzNDY2IDEuNjc0NzUsOC4yOTMzNiA4LjY3NTA4LDE0LjA2NTk4IDEwLjA1NTQxLDE0Ljg1ODYyIDQuOTAxNDcsMi44MTQ2MyAxMC44MTQ3OSw4LjE0OTgyIDEzLjA0NTc5LDE2LjA4ODMxIDYuNzU3NzksMjQuMDQ1OTEgMC44Nzk3Miw2OC40NTIxMiAwLjg3OTcyLDExMC42ODkzIDAsNi4wOTc4MiAxLjY2MDEsMzAuMTQ2NiAtMi4xNTU4OCwzMy45NjI1OSAtMi41NDA4NSwyLjU0MDgzIC0wLjI4MTYzLDEyLjk5MDY5IC0zLjQzNjc1LDE2LjE0Mzc3IGwgLTkuODQ5NDQsOS44NDMxMSBjIC0xMC4zNjcxNSwxMC4zNjA0NyAtMTEuNTkwMTcsNi41MjYxNCAtMTcuNzM4NDgsMTguODIyNzYgLTMuNTY3NzIsNy4xMzU0MyA1LjQwMjM1LDIwLjY3MjEgNy4zNTQzMiwyNC41NzYwMiAxLjkzMjE0LDMuODY0MyAtMS44NDIxNiw0Ljc3NzczIC0xLjc5MjM1LDcuNDQ2MjYgMC4yNTI4NiwxMy41NDQ4MyAyLjI5NzUsMzczLjkyNzEyIDIuMjk3NSwzNzMuOTI3MTIiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDM2NS4yNDAyMiw1MTkuNzc2MTIgNC4xMTU5OSw1MDIuMTUxNTgiCiAgICAgICBpZD0icGF0aDM4MDYiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAxMTYuNTMxNjUsNTA0LjE4Njk5IDMuODgwNTksMzEwLjk2NDM2IgogICAgICAgaWQ9InBhdGgzODMxIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDM4ODkiCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzE3LjY3NzYsNTc2LjQ4NTM5IDEzMC4xODc0MiwxLjUyNDQ0IGMgNC41MTA3OSwzLjI0MTY5IDIwLjM0NDcxLDcuOTY4NTMgMjcuNzQ0ODYsNC4yNjg0NCAzLjE1NTQ2LC0xLjU3NzcyIDkuNDE5LC01LjM4ODE3IDE0LjAyNDg5LC0zLjk2MzU1IDQuMjY2OTgsMS4zMTk4MSA2LjAxNjg5LDMuMTE2MzIgMTAuMzY2MjEsMy4wNDg4OSAxMC4zMDQwMywtMC4xNTk3NSAyMC4yMTE3LDAuMzg3NDEgMzAuNDg4ODYsMC4zMDQ4OSAxNzcuODkwOCwtMS40MjgyNyAzNTYuNTkwMzUsLTIuMTMyNDcgNTM0Ljc3NDU2LC0zLjA0ODg4IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2Nzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDc1LjMwNTAxLDU4Mi44ODgwNSBjIC0zLjQ0NDE4LDExLjM1MDY2IC0yLjEwMzQzLDEyLjQzMzczIDMuNjU4NjUsMjEuMDM3MzEgMy43OTQ0NSw1LjY2NTY0IDUwLjg2MjYxLDEzLjAzODQ1IDQxLjQ2NDg1LDI3LjEzNTA5IC0xMC41MzY5NywxNS44MDU0NyAtMjIuODk3NDUsLTUuNDc3NzIgLTMzLjg0MjYzLC0xLjgyOTMzIC01LjQ1MjM2LDEuODE3NDUgLTcuMzQ5MDEsNS40NTYzMSAtMy42NTg2Niw5LjE0NjY1IDIuODA2ODMsMi44MDY4NCA0LjA0OCwxLjgwMzk2IDYuNTIwMzQsNS4xMDA0MSIKICAgICAgIGlkPSJwYXRoMzkxMCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDMyLjAxMDgyLDYzNi44NTMzMyBjIDguMzE4OTksMTMuMTEwMTYgMTguODQ2MjEsMTQuNjM0NjUgMzUuNjcxOTYsMTQuNjM0NjUgMi45Mzg2NSwwIDcuODY5OTgsLTAuOTMzNzEgMTAuNjcxMTEsMCAxMS4zNTkxNywzLjc4NjM5IDI3LjE5Mzk4LDEwLjI3NTc3IDM2LjIwMTkzLDIxLjEyOTQ4IDguMjgwMDIsOS45NzY2MSAxMC4yNTI3OCwyMy44ODMwOCA3LjcwMjAyLDM3LjEwNDI0IC02LjE2OTg5LDMxLjk3OTk4IC0xNi43MTQzMSw1Ni45ODg1MyAtMTkuMDQzNTUsODYuNTY5MDUgLTEuMzQ3OTgsMTcuMTE4OCA0LjUwOTU3LDIyLjUzNTIyIDExLjA3MTQzLDMzLjkyODU3IDEwLjY3MDIzLDE4LjUyNjcyIDguNzI0NTMsMTQuMTk5NTUgOC41NzE0MywzNC4yODU3MiAtMC4xMzk2MywxOC4zMTk0NCAwLDYwLjI2Mzg1IDAsODAuNzE0MjkiCiAgICAgICBpZD0icGF0aDM5MTIiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDUyOC41MDgwNiw2NTguOTU3NzYgYyAtMTAuNjgxMjMsMC45MDQ1NCAtNy4xMDgwNCwtNS42MDI1NSAtMTAuODIzNTQsLTguMDc5NTYgLTQuNzg0NTQsLTMuMTg5NjkgLTEyLjIyNzA0LC0xLjI1MTA0IC0xNi43Njg4OCwtNS43OTI4OCAtMC42NjYxMiwtMC42NjYxMiAtOC44MDk2OSwtNC4xMDg3NyAtMTAuMTc0NDcsLTIuNzQzOTkgLTguMzY0NTksOC4zNjQ1OSAtMy4wNDg4OCwyMC41NTE4OCAtMy4wNDg4OCwzMy41Mzc3NCBsIDMuMDIyLDMzOS42OTc0MyIKICAgICAgIGlkPSJwYXRoMzkxNCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA1MTcuOTg5NDEsNjUxLjAzMDY1IGMgLTAuMjIxNzEsLTIuNzAxODQgMS45MDM0NiwtNS41NjIxMyAzLjM1Mzc3LC03LjAxMjQ1IDEuNzk5NDMsLTEuNzk5NDIgNi45MjI5NCwxLjAwNDE5IDguODQxNzgsLTAuOTE0NjYgMC4yODc2NSwtMC4yODc2NiAwLjg0MzI5LC0xMS4xNjQxIDAuMjI4NjYsLTEzLjU2NzUzIC0yLjA2NDgzLC04LjA3NDE2IC0yLjA1ODAxLC0yOC42NTY1OCAtMi4wNTgwMSwtMzguNzIwODYgbCAwLC03My4xNzMyNiIKICAgICAgIGlkPSJwYXRoMzkxNiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNTI4LjY2MDUsNjc1LjQyMTczIC0wLjQ1NzMzLC0zMS41NTU5NiIKICAgICAgIGlkPSJwYXRoMzk3NCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDc2Ni4zMTYyNSw1NzkuNjQ0MzEgMC40MzExOCwxMy43OTc2OCBjIDMuMTM2NDMsNC42NjkxNSAzLjAxODI0LDkuNjAwNjggMy4wMTgyNCwxNi4zODQ3NSBsIDAsMTU3LjM3OTgxIgogICAgICAgaWQ9InBhdGgzOTgyIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMTEyMi45MDAxLDc2NS45MTMwMyBjIC0yMDIuMzA2NjksNC42OTA1IC00MDMuNzQ0MDUsLTEuMTEzODEgLTYwNS45NTQ1NCwzLjM1MzkgLTEwLjg2MzYyLDAuMjQwMDIgLTMuMzYxNDcsLTguNTg2MyAtMjguNTM2OCwtOC41ODYzIgogICAgICAgaWQ9InBhdGgzOTg0IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA4NjAuMDA4MDUsNzM3LjA2NjUxIGMgMCwwIC05Ny40NDc1LDAuODU4MDYgLTE0Ny41Njg5MiwwLjg1ODA2IC01LjI2ODYxLDAgLTQuNTE1NDYsLTguMzI5ODYgLTcuMzAwODksLTguMzI5ODYgLTMuOTc0MzUsMCAtOC42MjkyNSwwLjAyMDEgLTEwLjUwOTQ4LDAuMDM1OSAtMi4zMzQ3NywwLjAxOTcgLTEuODEwOTQsOC4zNjU5NyAtNC4xNDU4LDguMzY2OTIgLTQ2LjE2ODk5LDAuMDE4OCAtMTY3LjQwNzY3LC0xLjMwNzk5IC0xNzUuMDUyNjMsLTEuMzA3OTkgLTQuNDI5NTUsMCAtOC41NzYyNywtNi40Mzk3MiAtMTMuMTMxOTgsLTYuNDM5NzIgLTEuMzYxMTUsMCAtNi4yMzg3MywwIC0xNC4zOTQ2NywwIgogICAgICAgaWQ9InBhdGgzOTg2IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJNIDY3NS4wMDcwMyw4MzEuMTc0MDIgNjc0LjM5NzI1LDMwOS40MDI5OSIKICAgICAgIGlkPSJwYXRoMzk4OCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDc5OS40MDE1NywzMTMuMDYxNjUgMS4yMTk1NSw0OTUuODY2NTMiCiAgICAgICBpZD0icGF0aDM5OTAiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA3MzYuNTk0NTIsMzEyLjQ1MTg4IC0xLjIxOTU1LDcxNi40ODgyMiIKICAgICAgIGlkPSJwYXRoMzk5MiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDUzMC4wMzA5NCw2NDMuNDU4NTkgMzkyLjM3MTU5LC0zLjAxODI1IgogICAgICAgaWQ9InBhdGg0MDQ4IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gODU5LjQ1MDYsMzE0LjkwMTI4IDEuMjkzNTQsNTA3Ljk4MDU4IgogICAgICAgaWQ9InBhdGg0MDUwIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjAuOTk5OTk5OTRweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gOTIxLjU0MDE3LDMxMC41ODk0OSAxLjcyNDcxLDUzMS43NTIyNyIKICAgICAgIGlkPSJwYXRoNDA1MiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDczNi4yODk2Myw0NTMuMzEwNCAxODUuNjc3MTUsLTAuMzA0ODkiCiAgICAgICBpZD0icGF0aDQxODciCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAxMDYwLjgxMDUsNTE0Ljk2NzY3IGMgMCwwIC0zNjMuMjgxMjYsLTUuNjI2MTggLTU0NC42NTA0MiwyLjUyMTc4IC00LjE3Nzc2LDAuMTg3NjkgLTEyLjUwMDQ0LDEuMDY3MTEgLTEyLjUwMDQ0LDEuMDY3MTEgLTEuNTcwOTUsMC4xMzQxIC0yLjAwMDkzLC0yLjMyNDk1IC0yLjU5MTU1LC0zLjUwNjIzIC0wLjA5NjcsLTAuMTkzNDMgLTcuMDYwODEsLTEuOTMzNCAtNy42MjIyMSwtMS4zNzE5OSAtMi44OTMxNCwyLjg5MzE0IC03LjYzMTY3LDQuMjQ4NjkgLTEyLjE5NTU1LDQuMTE2IEwgMzY5LjIwMTcsNTE0LjUzNjUiCiAgICAgICBpZD0icGF0aDQyNjEiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAzOTkuODE1MzEsNDc5LjYxMTEyIDExLjY0MTgsNS42MDUzIGMgMi45ODQxMiwxLjQzNjc5IDYuNTI4NzgsLTAuNDc3MTIgOS45MTcwOCwtMC40MzExOCBsIDEyNy4xOTczOSwxLjcyNDcxIgogICAgICAgaWQ9InBhdGg0MjYzIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gNTE5LjI1MTUxLDUxNy4xMjM1NyA1MTguODIwMzIsMzA4LjQzMzYyIgogICAgICAgaWQ9InBhdGg0MjY1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDMyLjkyNTQ5LDM4OS43MTQ5OCBjIDExLjA0NDk2LDAgMzUuNTMzMDcsMC42MTkyNyA0Mi41Nzk3OCwtMS4wMDM5NyA4LjQwNTIyLC0xLjkzNjE4IDcuMDY2LC02Ljk1Mzc4IDE0LjE5NzEyLC02Ljk1Mzc4IDcuODA5NSwwIDYuNTQyOTEsOC4wNjIzNyAyMC4xNDE3LDguMDYyMzcgMTMuOTkwNjgsMCA0NC45NzY4OSwwLjM3ODg2IDYzLjkzOTkyLDAuMzc4ODYgMTIuMDgzOTUsMCA4Mi4wMDI2NiwwLjMwNDg5IDkzLjYwMDgxLDAuMzA0ODkgOC43NjA0NywwIDEzLjE1OTcsLTIuMjg4MjcgMjEuMzQyMTksLTcuMDEyNDMgNy4xOTUxNSwtNC4xNTQxMyAyLjA1NDU5LC05LjQ5MTM3IDIwLjQyNzU0LC04Ljg0MTc3IDIzLjE0NTQsMC44MTgzMyAxMi42NDMzNCwxNC4wMjQ4NyAzMi4zMTgxOSwxNC4wMjQ4NyAyNS4zNTk1NCwwIDEzMC45OTkwMiwwIDE1MC45MTk4NSwwIDE0LjMzMjQ0LDAgLTQuMTE5MTEsLTEzLjExMDIxIDI5LjI2OTMsLTEzLjQxNTEiCiAgICAgICBpZD0icGF0aDQyNjkiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc3NzYyIgLz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjU4OC42Nzk1NyIKICAgICAgIHk9IjczNS44MDQ2MyIKICAgICAgIGlkPSJ0ZXh0NDMxMCIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMiIKICAgICAgICAgeD0iNTg4LjY3OTU3IgogICAgICAgICB5PSI3MzUuODA0NjMiPkxpbmNvbG48L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjY4Ni4zOTg1IgogICAgICAgeT0iNzY1LjYyODQyIgogICAgICAgaWQ9InRleHQ0MzEwLTciCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNiIKICAgICAgICAgeD0iNjg2LjM5ODUiCiAgICAgICAgIHk9Ijc2NS42Mjg0MiI+SGFycnk8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjcwOS44NzE4MyIKICAgICAgIHk9Ii04MDIuMzc3MzgiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMi02LTgiCiAgICAgICAgIHg9IjcwOS44NzE4MyIKICAgICAgICAgeT0iLTgwMi4zNzczOCI+V29vZGxhd248L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjU2Mi4xMTkyNiIKICAgICAgIHk9Ii03NzEuOTY4MTQiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xLTkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtOC0yIgogICAgICAgICB4PSI1NjIuMTE5MjYiCiAgICAgICAgIHk9Ii03NzEuOTY4MTQiPkVkZ2Vtb29yPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTguMzA0ODciCiAgICAgICB5PSItNzM4LjM2NjQ2IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTciCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtOC0yLTkiCiAgICAgICAgIHg9IjU5OC4zMDQ4NyIKICAgICAgICAgeT0iLTczOC4zNjY0NiI+T2xpdmVyPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTIuMTIyODYiCiAgICAgICB5PSItNjc3LjIwMzk4IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTctNSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNi04LTItOS00IgogICAgICAgICB4PSI1OTIuMTIyODYiCiAgICAgICAgIHk9Ii02NzcuMjAzOTgiPkhpbGxzaWRlPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTcuMzI3MDkiCiAgICAgICB5PSItODYyLjYxNDA3IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTctNS0zIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMi02LTgtMi05LTQtMSIKICAgICAgICAgeD0iNTk3LjMyNzA5IgogICAgICAgICB5PSItODYyLjYxNDA3Ij5Sb2NrPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1ODcuMzcwMTgiCiAgICAgICB5PSItOTI2LjEzNjYiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xLTktNy01LTMtMiIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNi04LTItOS00LTEtMyIKICAgICAgICAgeD0iNTg3LjM3MDE4IgogICAgICAgICB5PSItOTI2LjEzNjYiPldlYmI8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9Ijg3MS4xNjEwMSIKICAgICAgIHk9IjYzNy41NzUyIgogICAgICAgaWQ9InRleHQ0NDY1IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NDY3IgogICAgICAgICB4PSI4NzEuMTYxMDEiCiAgICAgICAgIHk9IjYzNy41NzUyIj5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI4NzMuODMyMjgiCiAgICAgICB5PSI1NzcuMDMyNDciCiAgICAgICBpZD0idGV4dDQ0NjUtMyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDQ2Ny00IgogICAgICAgICB4PSI4NzMuODMyMjgiCiAgICAgICAgIHk9IjU3Ny4wMzI0NyI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgaWQ9InRleHQ0NDkwIgogICAgICAgeT0iNTEwLjI2MTgxIgogICAgICAgeD0iODc1Ljk2NjQ5IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbgogICAgICAgICB5PSI1MTAuMjYxODEiCiAgICAgICAgIHg9Ijg3NS45NjY0OSIKICAgICAgICAgaWQ9InRzcGFuNDQ5MiIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+MjFzdDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iODgxLjMxNjU5IgogICAgICAgeT0iNDUwLjE5ODc2IgogICAgICAgaWQ9InRleHQ0NDk0IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NDk2IgogICAgICAgICB4PSI4ODEuMzE2NTkiCiAgICAgICAgIHk9IjQ1MC4xOTg3NiI+Mjl0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNjE1Ljc5MjQ4IgogICAgICAgeT0iMzg3Ljc0NzE2IgogICAgICAgaWQ9InRleHQ0NDY1LTMtMSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDQ2Ny00LTEiCiAgICAgICAgIHg9IjYxNS43OTI0OCIKICAgICAgICAgeT0iMzg3Ljc0NzE2Ij4zN3RoPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ1MTkiCiAgICAgICB5PSI0ODEuNjUyODYiCiAgICAgICB4PSI0ODQuNjkwMzciCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9IjQ4MS42NTI4NiIKICAgICAgICAgeD0iNDg0LjY5MDM3IgogICAgICAgICBpZD0idHNwYW40NTIxIgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIj4yNXRoPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1NjMuMDQ2NzUiCiAgICAgICB5PSI1MTMuMzYxMzMiCiAgICAgICBpZD0idGV4dDQ1MjMiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1MjUiCiAgICAgICAgIHg9IjU2My4wNDY3NSIKICAgICAgICAgeT0iNTEzLjM2MTMzIj4yMXN0PC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ1MjciCiAgICAgICB5PSI1NzcuODk0ODQiCiAgICAgICB4PSI1NjUuOTcxNSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNTc3Ljg5NDg0IgogICAgICAgICB4PSI1NjUuOTcxNSIKICAgICAgICAgaWQ9InRzcGFuNDUyOSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDUzMSIKICAgICAgIHk9Ii00NjAuNzMzMTIiCiAgICAgICB4PSI0MzMuNTgwNzUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii00NjAuNzMzMTIiCiAgICAgICAgIHg9IjQzMy41ODA3NSIKICAgICAgICAgaWQ9InRzcGFuNDUzMyIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+QW1pZG9uPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI0MDUuNTMwOTgiCiAgICAgICB5PSItNTIzLjU0MDE2IgogICAgICAgaWQ9InRleHQ0NTM1IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDUzNyIKICAgICAgICAgeD0iNDA1LjUzMDk4IgogICAgICAgICB5PSItNTIzLjU0MDE2Ij5BcmthbnNhczwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDUzOSIKICAgICAgIHk9Ii0zNzIuNTg1OTQiCiAgICAgICB4PSI3NDUuNDg0NjIiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii0zNzIuNTg1OTQiCiAgICAgICAgIHg9Ijc0NS40ODQ2MiIKICAgICAgICAgaWQ9InRzcGFuNDU0MSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+V2VzdDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNTk2LjcyODMzIgogICAgICAgeT0iLTUzMS4yNTkyOCIKICAgICAgIGlkPSJ0ZXh0NDU0MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NDUiCiAgICAgICAgIHg9IjU5Ni43MjgzMyIKICAgICAgICAgeT0iLTUzMS4yNTkyOCI+V2FjbzwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU1NSIKICAgICAgIHk9Ii0xMjIuNTAyOTUiCiAgICAgICB4PSI1OTUuNDM0ODEiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii0xMjIuNTAyOTUiCiAgICAgICAgIHg9IjU5NS40MzQ4MSIKICAgICAgICAgaWQ9InRzcGFuNDU1NyIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+TWF6aWU8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjY5NS43NzI5NSIKICAgICAgIHk9IjE2Mi4wNjg3NyIKICAgICAgIGlkPSJ0ZXh0NDU1OSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC43MDcxMDY3OCwwLjcwNzEwNjc4LC0wLjcwNzEwNjc4LDAuNzA3MTA2NzgsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NjEiCiAgICAgICAgIHg9IjY5NS43NzI5NSIKICAgICAgICAgeT0iMTYyLjA2ODc3Ij5ab288L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjI0MC41ODk5NyIKICAgICAgIHk9IjU3NC40NDU0MyIKICAgICAgIGlkPSJ0ZXh0NDU2MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDU2NSIKICAgICAgICAgeD0iMjQwLjU4OTk3IgogICAgICAgICB5PSI1NzQuNDQ1NDMiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU2NyIKICAgICAgIHk9IjUxMS42MzY2MyIKICAgICAgIHg9IjIwNi4wMzE3NSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNTExLjYzNjYzIgogICAgICAgICB4PSIyMDYuMDMxNzUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NjkiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPjIxc3Q8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjYyMC40NDMxMiIKICAgICAgIHk9Ii01MDYuNjgyMTkiCiAgICAgICBpZD0idGV4dDQ1NzEiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NTczIgogICAgICAgICB4PSI2MjAuNDQzMTIiCiAgICAgICAgIHk9Ii01MDYuNjgyMTkiPk5pbXM8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU4MyIKICAgICAgIHk9IjY5OC44NDAwOSIKICAgICAgIHg9IjM3MC4yMTY4NiIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNjk4Ljg0MDA5IgogICAgICAgICB4PSIzNzAuMjE2ODYiCiAgICAgICAgIGlkPSJ0c3BhbjQ1ODUiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1hcGxlPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSIzODQuMDg0MiIKICAgICAgIHk9IjY4MC44NTEzOCIKICAgICAgIGlkPSJ0ZXh0NDU5OSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDYwMSIKICAgICAgICAgeD0iMzg0LjA4NDIiCiAgICAgICAgIHk9IjY4MC44NTEzOCI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAzNjcuOTA4MTcsMTAwOS45NTk2IDI2My4wMTgzMywwIgogICAgICAgaWQ9InBhdGg0NjA1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDx0ZXh0CiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ2MDciCiAgICAgICB5PSItNDMzLjEzNzc2IgogICAgICAgeD0iNzM2LjI2NzQ2IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbgogICAgICAgICB5PSItNDMzLjEzNzc2IgogICAgICAgICB4PSI3MzYuMjY3NDYiCiAgICAgICAgIGlkPSJ0c3BhbjQ2MDkiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1lcmlkaWFuPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ5NzkiCiAgICAgICB5PSI2NDAuMjA1MjYiCiAgICAgICB4PSI1NzIuODMyMTUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9IjY0MC4yMDUyNiIKICAgICAgICAgeD0iNTcyLjgzMjE1IgogICAgICAgICBpZD0idHNwYW40OTgxIgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIj5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1NzUuMDg5NjYiCiAgICAgICB5PSI2NzAuOTAzNSIKICAgICAgIGlkPSJ0ZXh0NDk4MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDk4NSIKICAgICAgICAgeD0iNTc1LjA4OTY2IgogICAgICAgICB5PSI2NzAuOTAzNSI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNDk5LjQ4OTYyIgogICAgICAgeT0iMTAwOC42MDY5IgogICAgICAgaWQ9InRleHQ1MDQ3IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDQ5IgogICAgICAgICB4PSI0OTkuNDg5NjIiCiAgICAgICAgIHk9IjEwMDguNjA2OSI+NDd0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iMjE2LjY0NTQzIgogICAgICAgeT0iNzI1Ljk4Mjk3IgogICAgICAgaWQ9InRleHQ1MDUxIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDUzIgogICAgICAgICB4PSIyMTYuNjQ1NDMiCiAgICAgICAgIHk9IjcyNS45ODI5NyI+S2VsbG9nZzwvdHNwYW4+PC90ZXh0PgogICAgPGZsb3dSb290CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgaWQ9ImZsb3dSb290NTA1NSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6MThweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwyODcuMzYyMTgpIj48Zmxvd1JlZ2lvbgogICAgICAgICBpZD0iZmxvd1JlZ2lvbjUwNTciPjxyZWN0CiAgICAgICAgICAgaWQ9InJlY3Q1MDU5IgogICAgICAgICAgIHdpZHRoPSIzNDMuNTcxNDQiCiAgICAgICAgICAgaGVpZ2h0PSIxMDMuNTcxNDMiCiAgICAgICAgICAgeD0iMTkuMjg1NzE1IgogICAgICAgICAgIHk9IjE3LjE0Mjg1NyIKICAgICAgICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIgLz48L2Zsb3dSZWdpb24+PGZsb3dQYXJhCiAgICAgICAgIGlkPSJmbG93UGFyYTUwNjEiPjwvZmxvd1BhcmE+PC9mbG93Um9vdD4gICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDYwNy03IgogICAgICAgeT0iLTUwOC4xODk3MyIKICAgICAgIHg9Ijc3NC44NzU2MSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iLTUwOC4xODk3MyIKICAgICAgICAgeD0iNzc0Ljg3NTYxIgogICAgICAgICBpZD0idHNwYW40NjA5LTciCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1jQ2xlYW48L3RzcGFuPjwvdGV4dD4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzY0LjE1OTk5LDY1OC40Mjg5MSAyOTkuNTEwMjMsLTEuMDEwMTYgYyA2LjQ5ODcyLC0wLjAyMTkgNi45NzcxOSw5LjI1NDEyIDE2LjU5NjMxLDkuMzkyNDcgMTIuMDU0MjcsMC4xNzMzOSAyOS4xMTA4MywtMC41MzU3MiA1NC4xMTQzNywtMC4zMDExIgogICAgICAgaWQ9InBhdGg1NDQwIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjg3LjM2MjE4KSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjM3My45OTMwNCIKICAgICAgIHk9Ijk0NC4zNTc1NCIKICAgICAgIGlkPSJ0ZXh0NTA0Ny05IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDQ5LTMiCiAgICAgICAgIHg9IjM3My45OTMwNCIKICAgICAgICAgeT0iOTQ0LjM1NzU0Ij5NYWNBcnRodXI8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ2MDctNy0xIgogICAgICAgeT0iLTQ5MC4yNDU5NyIKICAgICAgIHg9Ijc4MC44NDYwNyIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iLTQ5MC4yNDU5NyIKICAgICAgICAgeD0iNzgwLjg0NjA3IgogICAgICAgICBpZD0idHNwYW40NjA5LTctOSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+U2VuZWNhPC90c3Bhbj48L3RleHQ+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDM2Ny42OTU1Myw1MzcuMjEwNiAxNDEuMjgzMDMsLTEuMDEwMTUgYyA2LjQ4OTk5LC0wLjA0NjQgMTIuNzgxMTQsNy4yMzU0NSAxOS4xOTI5LDcuMzIzNiA1NS45MjM2MiwwLjc2ODkgMTU4LjY4OTk3LC0wLjE3MzMzIDIzNi41MTQwMiwtMS4wMTAxNSA3LjgzOTU2LC0wLjA4NDMgMjIuNjMxNDcsLTE5Ljg1MzU1IDMwLjMwNDU3LC0yMC40NTU1OSAyMi4yNjU4OSwtMS4zNTE4MSA0NS4xNzk0NSwtMC41MDUwNyA2Ny42ODAyMiwtMC41MDUwNyAxNi4xNDczMSwtMC42MzI0MSAzLjYxMDE2LDIwLjcwODEzIDI2Ljc2OTA0LDIwLjcwODEzIGwgMjQzLjQ0Njc5LC0xLjAxMDE2IgogICAgICAgaWQ9InBhdGg1NDk2IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjg3LjM2MjE4KSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzY2NjY2MiIC8+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI2ODUuMjA4MTMiCiAgICAgICB5PSI4MjcuNTMwODIiCiAgICAgICBpZD0idGV4dDQzMTAtNy04IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtNiIKICAgICAgICAgeD0iNjg1LjIwODEzIgogICAgICAgICB5PSI4MjcuNTMwODIiPlBhd25lZTwvdHNwYW4+PC90ZXh0PgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSA1NTQuMjg1NzIsNzIxLjQyODU3IDU1MCw1NDMuMjE0MjkgNTQ3LjE0Mjg2LDEwMi41IDU0Ni43ODU3MiwyMy4yMTQyODUiCiAgICAgICBpZD0icGF0aDU1MTkiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwyODcuMzYyMTgpIiAvPgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNTI5LjYyNTMxIgogICAgICAgeT0iLTU1MC44NDc3OCIKICAgICAgIGlkPSJ0ZXh0NDU0My01IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDU0NS0wIgogICAgICAgICB4PSI1MjkuNjI1MzEiCiAgICAgICAgIHk9Ii01NTAuODQ3NzgiPkJyb2Fkd2F5PC90c3Bhbj48L3RleHQ+CiAgPC9nPgo8L3N2Zz4K\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showTooltip\":true,\"autocloseTooltip\":true,\"showTooltipAction\":\"click\",\"defaultCenterPosition\":[0,0]},\"title\":\"Markers Placement - Image Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"id\":\"c39f512a-21c6-6b06-3aa1-715262c6553d\",\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\\n widgetContext.map.locations[0], {\\n \\\"lat\\\": null,\\n \\\"lng\\\": null\\n }).then(function succes() {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}" + } + }, + { + "alias": "markers_placement_openstreetmap", + "name": "Markers Placement - OpenStreetMap", + "descriptor": { + "type": "latest", + "sizeX": 8.5, + "sizeY": 6, + "resources": [], + "templateHtml": "", + "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx, null, null, true);\n var createEntityLocation = {\n name: 'action.add',\n show: true,\n onAction: function($event) {\n self.ctx.map.selectEntity($event);\n },\n icon: 'add_location'\n };\n self.ctx.widgetActions = [createEntityLocation];\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('openstreet-map');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map');\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7867521952070078,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.7040053227577256,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}

Delete\",\"markerImageSize\":34,\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"color\":\"#fe7569\",\"mapProvider\":\"OpenStreetMap.Mapnik\",\"showTooltip\":true,\"autocloseTooltip\":true,\"defaultCenterPosition\":[0,0],\"customProviderTileUrl\":\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\"showTooltipAction\":\"click\",\"polygonKeyName\":\"coordinates\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"zoomOnClick\":true,\"showCoverageOnHover\":true,\"animate\":true,\"maxClusterRadius\":80,\"removeOutsideVisibleBounds\":true,\"defaultZoomLevel\":5},\"title\":\"Markers Placement - OpenStreetMap\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"id\":\"54c293c4-9ca6-e34f-dc6a-0271944c1c66\",\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\\n widgetContext.map.locations[0], {\\n \\\"lat\\\": null,\\n \\\"lng\\\": null\\n }).then(function succes() {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}" + } + }, + { + "alias": "markers_placement_google_maps", + "name": "Markers Placement - Google Maps", + "descriptor": { + "type": "latest", + "sizeX": 8.5, + "sizeY": 6, + "resources": [], + "templateHtml": "", + "templateCss": ".error {\n color: red;\n}\n.tb-labels {\n color: #222;\n font: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n text-align: center;\n width: 200px;\n white-space: nowrap;\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('google-map', false, self.ctx, null, null, true);\n var createEntityLocation = {\n name: 'action.add',\n show: true,\n onAction: function($event) {\n self.ctx.map.selectEntity($event);\n },\n icon: 'add_location'\n };\n self.ctx.widgetActions = [createEntityLocation];\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('google-map');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('google-map');\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}

Delete\",\"markerImageSize\":34,\"gmDefaultMapType\":\"roadmap\",\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"colorFunction\":\"\\n\",\"color\":\"#fe7569\",\"showTooltip\":true,\"autocloseTooltip\":true,\"defaultCenterPosition\":[0,0],\"showTooltipAction\":\"click\",\"polygonKeyName\":\"coordinates\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"zoomOnClick\":true,\"gridSize\":60,\"defaultZoomLevel\":5},\"title\":\"Markers Placement - Google Maps\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"id\":\"8d3c0156-0a14-7a6f-0ddd-0ec16b9ffc91\",\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\\n widgetContext.map.locations[0], {\\n \\\"lat\\\": null,\\n \\\"lng\\\": null\\n }).then(function succes() {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}" + } } ] } diff --git a/ui/src/app/widget/lib/add-entity-panel.scss b/ui/src/app/widget/lib/add-entity-panel.scss new file mode 100644 index 0000000000..6d93e2a4da --- /dev/null +++ b/ui/src/app/widget/lib/add-entity-panel.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-add-entity-panel { + min-width: 150px; + max-height: 200px; + overflow: hidden; + overflow-y: auto; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); +} diff --git a/ui/src/app/widget/lib/add-entity-panel.tpl.html b/ui/src/app/widget/lib/add-entity-panel.tpl.html new file mode 100644 index 0000000000..738a921679 --- /dev/null +++ b/ui/src/app/widget/lib/add-entity-panel.tpl.html @@ -0,0 +1,22 @@ + + + + {{ entity.entityLabel || entity.name }} + + diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index 0cfdefd180..0f0a118f3d 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -29,7 +29,7 @@ export default class TbGoogleMap { this.tooltips = []; this.defaultMapType = gmDefaultMapType; this.defaultCenterPosition = defaultCenterPosition; - this.isMarketCluster = markerClusteringSetting.isMarketCluster; + this.isMarketCluster = markerClusteringSetting && markerClusteringSetting.isMarketCluster; function clearGlobalId() { if (gmGlobals.loadingGmId && gmGlobals.loadingGmId === tbMap.mapId) { @@ -234,17 +234,19 @@ export default class TbGoogleMap { /* eslint-enable no-undef */ /* eslint-disable no-undef */ - createMarker(location, dsIndex, settings, onClickListener, markerArgs) { + createMarker(location, dsIndex, settings, onClickListener, markerArgs, onDragendListener) { var marker; if (settings.showLabel) { marker = new MarkerWithLabel({ position: location, labelContent: '
'+settings.labelText+'
', - labelClass: "tb-labels" + labelClass: "tb-labels", + draggable: settings.drraggable, }); } else { marker = new google.maps.Marker({ position: location, + draggable: settings.drraggable, }); } var gMap = this; @@ -268,6 +270,10 @@ export default class TbGoogleMap { marker.addListener('click', onClickListener); } + if (onDragendListener) { + marker.addListener('dragend', onDragendListener); + } + return marker; } @@ -476,4 +482,8 @@ export default class TbGoogleMap { return this.tooltips; } + getCenter() { + return this.map.getCenter().toJSON(); + } + } diff --git a/ui/src/app/widget/lib/image-map.js b/ui/src/app/widget/lib/image-map.js index 3b47be27ec..960de83691 100644 --- a/ui/src/app/widget/lib/image-map.js +++ b/ui/src/app/widget/lib/image-map.js @@ -300,12 +300,14 @@ export default class TbImageMap { onMarkerIconReady(iconInfo); } - createMarker(position, dsIndex, settings, onClickListener, markerArgs) { + createMarker(position, dsIndex, settings, onClickListener, markerArgs, onDragendListener) { var pos = this.posFunction(position.x, position.y); var x = pos.x * this.width; var y = pos.y * this.height; var location = this.pointToLatLng(x, y); - var marker = L.marker(location, {});//.addTo(this.map); + var marker = L.marker(location, { + draggable: settings.drraggable + });//.addTo(this.map); marker.position = position; marker.offsetX = settings.markerOffsetX; marker.offsetY = settings.markerOffsetY; @@ -327,10 +329,30 @@ export default class TbImageMap { if (onClickListener) { marker.on('click', onClickListener); } + if (onDragendListener) { + marker.on('dragend', ($event) => { + let newMarkerPosition = this.latLngToPoint(marker.getLatLng()); + marker.position.x = this.constructor.calculateNewPosition(newMarkerPosition.x, this.width); + marker.position.y = this.constructor.calculateNewPosition(newMarkerPosition.y, this.height); + this.setMarkerPosition(marker, marker.position); + onDragendListener($event); + }); + } this.markers.push(marker); return marker; } + static calculateNewPosition(positon, imageSize) { + let newPosition = positon / imageSize; + if (newPosition < 0) { + newPosition = 0; + } else if (newPosition > 1) { + newPosition = 1; + } + return newPosition; + } + + updateMarkers() { this.markers.forEach((marker) => { this.updateMarkerLocation(marker); @@ -439,6 +461,10 @@ export default class TbImageMap { return this.tooltips; } + getCenter() { + return this.map.getCenter(); + } + } class Position { diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js index e6d2dfc0e7..005d50afaf 100644 --- a/ui/src/app/widget/lib/map-widget2.js +++ b/ui/src/app/widget/lib/map-widget2.js @@ -21,10 +21,12 @@ import TbImageMap from './image-map'; import TbTencentMap from './tencent-map'; import {processPattern, arraysEqual, toLabelValueMap, fillPattern, fillPatternWithActions} from './widget-utils'; +import addEntityPanelTemplate from './add-entity-panel.tpl.html'; +import './add-entity-panel.scss'; export default class TbMapWidgetV2 { - constructor(mapProvider, drawRoutes, ctx, useDynamicLocations, $element) { + constructor(mapProvider, drawRoutes, ctx, useDynamicLocations, $element, isEdit) { var tbMap = this; this.ctx = ctx; this.mapProvider = mapProvider; @@ -33,6 +35,7 @@ export default class TbMapWidgetV2 { } this.utils = ctx.$scope.$injector.get('utils'); this.drawRoutes = drawRoutes; + this.isEdit = isEdit ? isEdit : false; this.markers = []; this.polygons = []; if (this.drawRoutes) { @@ -292,6 +295,112 @@ export default class TbMapWidgetV2 { } } + selectEntity($event) { + var tbMap = this; + + function setDefaultPosition(entity) { + let position = tbMap.map.getCenter(); + if (tbMap.mapProvider === "image-map") { + position = tbMap.map.latLngToPoint(position); + position.lat = position.x / tbMap.map.width; + position.lng = position.y / tbMap.map.height; + } + + tbMap.saveMarkerLocation( + entity, + locationsWithoutMarker[entitiesWithoutPosition.indexOf(entity)], + position + ); + } + + const element = angular.element($event.target); + const $mdPanel = this.ctx.$scope.$injector.get('$mdPanel'); + const $document = this.ctx.$scope.$injector.get('$document'); + let position = $mdPanel.newPanelPosition() + .relativeTo(element) + .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW); + + let locationsWithoutMarker = this.locations.filter((location) => !location.marker); + let entitiesWithoutPosition = []; + for (let i = 0; i < locationsWithoutMarker.length; i++) { + entitiesWithoutPosition.push(this.subscription.datasources[locationsWithoutMarker[i].dsIndex]); + } + + if(entitiesWithoutPosition.length === 1){ + setDefaultPosition(entitiesWithoutPosition[0]); + } else { + let config = { + attachTo: angular.element($document[0].body), + controller: addEntityPanelController, + controllerAs: 'vm', + templateUrl: addEntityPanelTemplate, + panelClass: 'tb-add-entity-panel', + position: position, + fullscreen: false, + locals: { + 'entities': entitiesWithoutPosition, + 'onClose': setDefaultPosition + }, + openFrom: $event, + clickOutsideToClose: true, + escapeToClose: true, + focusOnOpen: false + }; + $mdPanel.open(config); + } + } + + saveMarkerLocation(datasource, location, coordinate) { + var tbMap = this; + + const types = tbMap.ctx.$scope.$injector.get('types'); + const $q = tbMap.ctx.$scope.$injector.get('$q'); + const attributeService = tbMap.ctx.$scope.$injector.get('attributeService'); + + let attributesLocation = []; + let timeseriesLocation = []; + let promises = []; + + let dataKeys = datasource.dataKeys; + for (let i = 0; i < dataKeys.length; i++) { + if (dataKeys[i].name === location.settings.latKeyName || dataKeys[i].name === location.settings.lngKeyName) { + let newLocation = { + key: dataKeys[i].name, + value: dataKeys[i].name === location.settings.latKeyName ? coordinate.lat : coordinate.lng + }; + if (dataKeys[i].type === types.dataKeyType.attribute) { + attributesLocation.push(newLocation); + } else if (dataKeys[i].type === types.dataKeyType.timeseries) { + timeseriesLocation.push(newLocation); + } + } + } + + if (attributesLocation.length > 0) { + promises.push(attributeService.saveEntityAttributes( + datasource.entityType, + datasource.entityId, + types.attributesScope.server.value, + attributesLocation, + { + ignoreLoading: true + } + )) + } + if (timeseriesLocation.length > 0) { + promises.push(attributeService.saveEntityTimeseries( + datasource.entityType, + datasource.entityId, + "scope", + timeseriesLocation, + { + ignoreLoading: true + } + )) + } + return $q.all([promises]); + } + update() { var tbMap = this; @@ -411,7 +520,10 @@ export default class TbMapWidgetV2 { function (event) { tbMap.callbacks.onLocationClick(location); locationRowClick(event, location); - }, [location.dsIndex]); + }, [location.dsIndex], + function (event) { + markerDragend(event, location) + }); tbMap.markers.push(location.marker); changed = true; } else { @@ -424,6 +536,22 @@ export default class TbMapWidgetV2 { return changed; } + function markerDragend($event, location) { + if (location.settings.drraggable) { + let position = tbMap.map.getMarkerPosition(location.marker); + if (tbMap.mapProvider === "image-map") { + position.lat = position.x; + position.lng = position.y; + delete position.x; + delete position.y; + } else if (tbMap.mapProvider === "google-map") { + position = position.toJSON(); + } + + tbMap.saveMarkerLocation(tbMap.subscription.datasources[location.dsIndex], location, position); + } + } + function locationRowClick($event, location) { var descriptors = tbMap.ctx.actionsApi.getActionDescriptors('markerClick'); if (descriptors.length) { @@ -567,6 +695,7 @@ export default class TbMapWidgetV2 { location.settings.tooltipPattern = tbMap.utils.createLabelFromDatasource(currentDatasource, location.settings.tooltipPattern); location.settings.tooltipReplaceInfo = processPattern(location.settings.tooltipPattern, datasources, currentDatasourceIndex); } + location.settings.drraggable = tbMap.isEdit; tbMap.locations.push(location); updateLocation(location, data, dataMap); if (!tbMap.locationSettings.useDefaultCenterPosition) { @@ -1626,3 +1755,16 @@ const imageMapSettingsSchema = } ] }; + +/*@ngInject*/ +function addEntityPanelController(mdPanelRef, entities) { + var vm = this; + vm.entities = entities; + vm.selectEntity = selectEntity; + + function selectEntity(entity) { + mdPanelRef.close().then(() => { + this.onClose(entity); + }); + } +} diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js index abb6ccca31..c3b54e7ed8 100644 --- a/ui/src/app/widget/lib/openstreet-map.js +++ b/ui/src/app/widget/lib/openstreet-map.js @@ -29,7 +29,7 @@ export default class TbOpenStreetMap { this.dontFitMapBounds = dontFitMapBounds; this.minZoomLevel = minZoomLevel; this.tooltips = []; - this.isMarketCluster = markerClusteringSetting.isMarketCluster; + this.isMarketCluster = markerClusteringSetting && markerClusteringSetting.isMarketCluster; if (!mapProvider) { mapProvider = { @@ -150,8 +150,10 @@ export default class TbOpenStreetMap { onMarkerIconReady(iconInfo); } - createMarker(location, dsIndex, settings, onClickListener, markerArgs) { - var marker = L.marker(location, {}); + createMarker(location, dsIndex, settings, onClickListener, markerArgs, onDragendListener) { + var marker = L.marker(location, { + draggable: settings.drraggable + }); var opMap = this; this.createMarkerIcon(marker, settings, (iconInfo) => { marker.setIcon(iconInfo.icon); @@ -171,6 +173,10 @@ export default class TbOpenStreetMap { marker.on('click', onClickListener); } + if (onDragendListener) { + marker.on('dragend', onDragendListener); + } + return marker; } @@ -326,4 +332,8 @@ export default class TbOpenStreetMap { return this.tooltips; } + getCenter() { + return this.map.getCenter(); + } + } diff --git a/ui/src/app/widget/lib/tencent-map.js b/ui/src/app/widget/lib/tencent-map.js index 2acead223d..0352822f4f 100644 --- a/ui/src/app/widget/lib/tencent-map.js +++ b/ui/src/app/widget/lib/tencent-map.js @@ -28,7 +28,7 @@ export default class TbTencentMap { this.tooltips = []; this.defaultMapType = tmDefaultMapType; this.defaultCenterPosition =defaultCenterPosition; - this.isMarketCluster = markerClusteringSetting.isMarketCluster; + this.isMarketCluster = markerClusteringSetting && markerClusteringSetting.isMarketCluster; function clearGlobalId() { if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) { @@ -239,7 +239,7 @@ export default class TbTencentMap { /* eslint-enable no-undef */ /* eslint-disable no-undef */ - createMarker(location, dsIndex, settings, onClickListener, markerArgs) { + createMarker(location, dsIndex, settings, onClickListener, markerArgs, onDragendListener) { var marker = new qq.maps.Marker({ position: location }); @@ -260,7 +260,8 @@ export default class TbTencentMap { visible: true, position: location, map: tMap.map, - zIndex: 1000 + zIndex: 1000, + draggable: settings.drraggable }); } }); @@ -273,6 +274,10 @@ export default class TbTencentMap { qq.maps.event.addListener(marker, 'click', onClickListener); } + if (onDragendListener) { + qq.maps.event.addListener(marker, 'dragend', onDragendListener); + } + return marker; } @@ -487,4 +492,8 @@ export default class TbTencentMap { return this.tooltips; } + getCenter() { + return this.map.getCenter(); + } + } From 1059c21eab2e78363d79bae99f1cd37c90f1ad9f Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Fri, 8 Nov 2019 14:19:49 +0200 Subject: [PATCH 036/120] Fix calculate trip to big interval time (#2161) --- .../tripAnimation/trip-animation-widget.js | 274 ++++++++---------- .../trip-animation-widget.tpl.html | 12 +- 2 files changed, 136 insertions(+), 150 deletions(-) diff --git a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js index 10fc548f5a..b470be72f9 100644 --- a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js +++ b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js @@ -128,7 +128,8 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt vm.index = 0; vm.dsIndex = 0; vm.minTime = 0; - vm.maxTime = 0; + vm.minTimeIndex = 0; + vm.maxTimeIndex = 0; vm.isPlaying = false; vm.trackingLine = { "type": "FeatureCollection", @@ -200,10 +201,10 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt vm.moveNext = function () { vm.stopPlay(); if (vm.staticSettings.usePointAsAnchor) { - let newIndex = vm.maxTime; - for (let index = vm.index+1; index < vm.maxTime; index++) { + let newIndex = vm.maxTimeIndex; + for (let index = vm.index + 1; index < vm.maxTimeIndex; index++) { if (vm.trips.some(function (trip) { - return trip.timeRange[index].hasAnchor; + return calculateCurrentDate(trip.timeRange, index).hasAnchor; })) { newIndex = index; break; @@ -216,27 +217,27 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt vm.movePrev = function () { vm.stopPlay(); if (vm.staticSettings.usePointAsAnchor) { - let newIndex = vm.minTime; - for (let index = vm.index-1; index > vm.minTime; index--) { + let newIndex = vm.minTimeIndex; + for (let index = vm.index - 1; index > vm.minTimeIndex; index--) { if (vm.trips.some(function (trip) { - return trip.timeRange[index].hasAnchor; - })) { + return calculateCurrentDate(trip.timeRange, index).hasAnchor; + })) { newIndex = index; break; } } moveToIndex(newIndex); - } else moveInc(-1); + } else moveInc(-1); }; vm.moveStart = function () { vm.stopPlay(); - moveToIndex(vm.minTime); + moveToIndex(vm.minTimeIndex); }; vm.moveEnd = function () { vm.stopPlay(); - moveToIndex(vm.maxTime); + moveToIndex(vm.maxTimeIndex); }; vm.stopPlay = function () { @@ -252,8 +253,9 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } function moveToIndex(newIndex) { - if (newIndex > vm.maxTime || newIndex < vm.minTime) return; + if (newIndex > vm.maxTimeIndex || newIndex < vm.minTimeIndex) return; vm.index = newIndex; + vm.animationTime = vm.minTime + vm.index * vm.staticSettings.normalizationStep; recalculateTrips(); } @@ -263,12 +265,6 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt }) } - function findAngle(lat1, lng1, lat2, lng2) { - let angle = Math.atan2(0, 0) - Math.atan2(lat2 - lat1, lng2 - lng1); - angle = angle * 180 / Math.PI; - return parseInt(angle.toFixed(2)); - } - function initialize() { $scope.currentDate = $filter('date')(0, "yyyy.MM.dd HH:mm:ss"); @@ -445,7 +441,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } } - function configureTripSettings(trip, index, apply) { + function configureTripSettings(trip, apply) { trip.settings = {}; trip.settings.color = calculateColor(trip); trip.settings.polygonColor = calculatePolygonColor(trip); @@ -478,17 +474,17 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let labelText = vm.staticSettings.label; if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) { try { - labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dsIndex); + labelText = vm.staticSettings.labelFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dsIndex); } catch (e) { labelText = null; } } labelText = vm.utils.createLabelFromDatasource(trip.dataSource, labelText); labelReplaceInfo = processPattern(labelText, vm.ctx.datasources, trip.dSIndex); - label = fillPattern(labelText, labelReplaceInfo, trip.timeRange[vm.index]); + label = fillPattern(labelText, labelReplaceInfo, calculateCurrentDate(trip.timeRange, vm.index)); if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) { try { - labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex); + labelText = vm.staticSettings.labelFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { labelText = null; } @@ -504,14 +500,14 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let tooltipText = vm.staticSettings.tooltipPattern; if (vm.staticSettings.useTooltipFunction && angular.isDefined(vm.staticSettings.tooltipFunction)) { try { - tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex); + tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { tooltipText = null; } } tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText); tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex); - tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[vm.index]); + tooltip = fillPattern(tooltipText, tooltipReplaceInfo, calculateCurrentDate(trip.timeRange, vm.index)); tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null); } @@ -525,14 +521,14 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let tooltipText = vm.staticSettings.polygonTooltipPattern; if (vm.staticSettings.usePolygonTooltipFunction && angular.isDefined(vm.staticSettings.polygonTooltipFunction)) { try { - tooltipText = vm.staticSettings.polygonTooltipFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex); + tooltipText = vm.staticSettings.polygonTooltipFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { tooltipText = null; } } tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText); tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex); - tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[vm.index]); + tooltip = fillPattern(tooltipText, tooltipReplaceInfo, calculateCurrentDate(trip.timeRange, vm.index)); tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null); } @@ -546,14 +542,14 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let tooltipText = vm.staticSettings.tooltipPattern; if (vm.staticSettings.useTooltipFunction && angular.isDefined(vm.staticSettings.tooltipFunction)) { try { - tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, trip.timeRange[index], trip.dSIndex); + tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, index), trip.dSIndex); } catch (e) { tooltipText = null; } } tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText); tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex); - tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[index]); + tooltip = fillPattern(tooltipText, tooltipReplaceInfo, calculateCurrentDate(trip.timeRange, index)); tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null); } @@ -565,7 +561,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let colorFn; if (vm.staticSettings.usePathColorFunction && angular.isDefined(vm.staticSettings.colorFunction)) { try { - colorFn = vm.staticSettings.colorFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex); + colorFn = vm.staticSettings.colorFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { colorFn = null; } @@ -581,7 +577,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt let colorFn; if (vm.staticSettings.usePolygonColorFunction && angular.isDefined(vm.staticSettings.polygonColorFunction)) { try { - colorFn = vm.staticSettings.polygonColorFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex); + colorFn = vm.staticSettings.polygonColorFunction(vm.ctx.data, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { colorFn = null; } @@ -597,7 +593,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt if (vm.staticSettings.useMarkerImageFunction && angular.isDefined(vm.staticSettings.markerImageFunction)) { let rawIcon; try { - rawIcon = vm.staticSettings.markerImageFunction(vm.ctx.data, vm.staticSettings.markerImages, trip.timeRange[vm.index], trip.dSIndex); + rawIcon = vm.staticSettings.markerImageFunction(vm.ctx.data, vm.staticSettings.markerImages, calculateCurrentDate(trip.timeRange, vm.index), trip.dSIndex); } catch (e) { rawIcon = null; } @@ -658,8 +654,8 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt }); vm.initBounds = true; } - let normalizedTimeRange = createNormalizedTime(vm.data, vm.staticSettings.normalizationStep); - createNormalizedTrips(normalizedTimeRange, vm.datasources); + createNormalizedTime(vm.data, vm.staticSettings.normalizationStep); + createNormalizedTrips(vm.datasources, vm.data, vm.staticSettings.normalizationStep); createTripsOnMap(apply); if (vm.initBounds && !vm.initTrips) { vm.trips.forEach(function (trip) { @@ -701,127 +697,82 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt function createNormalizedTime(data, step) { if (!step) step = 1000; - let max_time = null; - let min_time = null; - let normalizedArray = []; - if (data && data.length > 0) { - vm.data.forEach(function (data) { - if (data.data.length > 0) { - data.data.forEach(function (sData) { - if (max_time === null) { - max_time = sData[0]; - } else if (max_time < sData[0]) { - max_time = sData[0] - } - if (min_time === null) { - min_time = sData[0]; - } else if (min_time > sData[0]) { - min_time = sData[0]; - } - }) + let max_time = -Infinity; + let min_time = Infinity; + if (data) { + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].data.length; j++) { + if (max_time < data[i].data[j][0]) { + max_time = data[i].data[j][0] + } + if (min_time > data[i].data[j][0]) { + min_time = data[i].data[j][0]; + } } - }); - for (let i = min_time; i < max_time; i += step) { - normalizedArray.push({ts: i, formattedTs: $filter('date')(i, 'medium')}); - - } - if (normalizedArray[normalizedArray.length - 1] && normalizedArray[normalizedArray.length - 1].ts !== max_time) { - normalizedArray.push({ts: max_time, formattedTs: $filter('date')(max_time, 'medium')}); } } - vm.maxTime = normalizedArray.length - 1; - //vm.minTime = vm.maxTime > 1 ? 1 : 0; - if (vm.index < vm.minTime) { - vm.index = vm.minTime; - } else if (vm.index > vm.maxTime) { - vm.index = vm.maxTime; + vm.minTime = vm.animationTime = min_time; + if(min_time === Infinity){ + vm.animationTime = null; + } else { + vm.animationTime = min_time + } + vm.maxTimeIndex = Math.ceil((max_time - min_time) / step); + if (vm.index < vm.minTimeIndex) { + vm.index = vm.minTimeIndex; + } else if (vm.index > vm.maxTimeIndex) { + vm.index = vm.maxTimeIndex; } - return normalizedArray; } - function createNormalizedTrips(timeRange, dataSources) { + function createNormalizedTrips(dataSources, data, step) { vm.trips = []; - if (timeRange && timeRange.length > 0 && dataSources && dataSources.length > 0 && vm.data && vm.data.length > 0) { - dataSources.forEach(function (dS, index) { + step = step || 1000; + if (dataSources && data) { + for (let i = 0; i < dataSources.length; i++) { vm.trips.push({ - dataSource: dS, - dSIndex: index, - timeRange: angular.copy(timeRange) + dataSource: dataSources[i], + dSIndex: i, + timeRange: {} }) - }); + } - vm.data.forEach(function (data) { - let ds = data.datasource; + for (let i = 0; i < data.length; i++) { + let ds = data[i].datasource; let tripIndex = vm.trips.findIndex(function (el) { return el.dataSource.entityId === ds.entityId; }); - if (tripIndex > -1) { - createNormalizedValue(data.data, data.dataKey.label, vm.trips[tripIndex].timeRange); + createNormalizedValue(data[i].data, data[i].dataKey.label, vm.trips[tripIndex].timeRange, step); } - }) + } } createNormalizedLatLngs(); } - function createNormalizedValue(dataArray, dataKey, timeRangeArray) { - timeRangeArray.forEach(function (timeStamp) { - let targetTDiff = null; - let value = null; - for (let i = 0; i < dataArray.length; i++) { - let tDiff = dataArray[i][0] - timeStamp.ts; - if (targetTDiff === null || (tDiff <= 0 && targetTDiff < tDiff)) { - targetTDiff = tDiff; - value = dataArray[i][1]; - - } - } - if (value !== null) timeStamp[dataKey] = value; - }); + function createNormalizedValue(dataArray, dataKey, timeRange, step) { + for (let i = 0; i < dataArray.length; i++) { + let normalizeTime = vm.minTime + Math.ceil((dataArray[i][0] - vm.minTime) / step) * step; + timeRange[normalizeTime] = timeRange[normalizeTime] || {}; + timeRange[normalizeTime][dataKey] = dataArray[i][1]; + } } function createNormalizedLatLngs() { - vm.trips.forEach(function (el) { - el.latLngs = []; - el.timeRange.forEach(function (data) { - let lat = data[vm.staticSettings.latKeyName]; - let lng = data[vm.staticSettings.lngKeyName]; - if (lat && lng && vm.map) { - data.latLng = vm.map.createLatLng(lat, lng); - } - el.latLngs.push(data.latLng); - }); - addAngleForTrip(el); - }) - } - - function addAngleForTrip(trip) { - if (trip.timeRange && trip.timeRange.length > 0) { - trip.timeRange.forEach(function (point, index) { - let nextPoint, prevPoint; - nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : trip.timeRange[index + 1]; - prevPoint = index === 0 ? trip.timeRange[0] : trip.timeRange[index - 1]; - let nextLatLng = { - lat: nextPoint[vm.staticSettings.latKeyName], - lng: nextPoint[vm.staticSettings.lngKeyName] - }; - let prevLatLng = { - lat: prevPoint[vm.staticSettings.latKeyName], - lng: prevPoint[vm.staticSettings.lngKeyName] - }; - if (nextLatLng.lat === prevLatLng.lat && nextLatLng.lng === prevLatLng.lng) { - if (angular.isNumber(prevPoint.h)) { - point.h = prevPoint.h; - } else { - point.h = vm.staticSettings.rotationAngle; + vm.trips.forEach(function (item) { + item.latLngs = []; + for (let timestamp in item.timeRange) { + if(Object.prototype.hasOwnProperty.call(item.timeRange, timestamp)) { + let lat = item.timeRange[timestamp][vm.staticSettings.latKeyName]; + let lng = item.timeRange[timestamp][vm.staticSettings.lngKeyName]; + if (lat && lng && vm.map) { + item.timeRange[timestamp].latLng = vm.map.createLatLng(lat, lng); } - } else { - point.h = findAngle(prevLatLng.lat, prevLatLng.lng, nextLatLng.lat, nextLatLng.lng); - point.h += vm.staticSettings.rotationAngle; + item.latLngs.push(item.timeRange[timestamp].latLng); } - }); - } + } + }); } function createPointPopup(point, index, trip) { @@ -834,14 +785,14 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt function createTripsOnMap(apply) { if (vm.trips.length > 0) { vm.trips.forEach(function (trip) { - configureTripSettings(trip, vm.index, apply); - if (trip.timeRange.length > 0 && trip.latLngs.every(el => angular.isDefined(el))) { + configureTripSettings(trip, apply); + if (Object.keys(trip.timeRange).length > 0 && trip.latLngs.every(el => angular.isDefined(el))) { if (vm.staticSettings.showPoints) { trip.points = []; - trip.timeRange.forEach(function (tRange, index) { - if (tRange && tRange.latLng - && (!vm.staticSettings.usePointAsAnchor || vm.staticSettings.pointAsAnchorFunction(vm.ctx.data, tRange, trip.dSIndex))) { - let point = L.circleMarker(tRange.latLng, { + Object.keys(trip.timeRange).forEach(function (tRange, index) { + if (trip.timeRange[tRange] && trip.timeRange[tRange].latLng + && (!vm.staticSettings.usePointAsAnchor || vm.staticSettings.pointAsAnchorFunction(vm.ctx.data, trip.timeRange[tRange], trip.dSIndex))) { + let point = L.circleMarker(trip.timeRange[tRange].latLng, { color: trip.settings.pointColor, radius: trip.settings.pointSize }).addTo(vm.map.map); @@ -852,7 +803,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt showHidePointTooltip(calculatePointTooltip(trip, index), index); }); } - if (vm.staticSettings.usePointAsAnchor) tRange.hasAnchor = true; + if (vm.staticSettings.usePointAsAnchor) trip.timeRange[tRange].hasAnchor = true; trip.points.push(point); } }); @@ -872,7 +823,8 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt polygon: false, pathOptions: { color: vm.staticSettings.useDecoratorCustomColor ? vm.staticSettings.decoratorCustomColor : trip.settings.color, - stroke: true} + stroke: true + } }) } ], @@ -882,8 +834,8 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } - if (trip.timeRange && trip.timeRange.length && angular.isUndefined(trip.marker)) { - trip.marker = L.marker(trip.timeRange[vm.index].latLng); + if (trip.timeRange && Object.keys(trip.timeRange).length && angular.isUndefined(trip.marker)) { + trip.marker = L.marker(calculateCurrentDate(trip.timeRange, vm.index).latLng); trip.marker.setZIndexOffset(1000); trip.marker.setIcon(vm.staticSettings.icon); trip.marker.setRotationOrigin('center center'); @@ -895,7 +847,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } } - if (vm.staticSettings.showPolygon && angular.isDefined(trip.timeRange[vm.index][vm.staticSettings.polKeyName])) { + if (vm.staticSettings.showPolygon && angular.isDefined(calculateCurrentDate(trip.timeRange, vm.index)[vm.staticSettings.polKeyName])) { let polygonSettings = { fill: true, fillColor: trip.settings.polygonColor, @@ -904,7 +856,7 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt fillOpacity: trip.settings.polygonOpacity, opacity: trip.settings.polygonStrokeOpacity }; - let polygonLatLngsRaw = mapPolygonArray(angular.fromJson(trip.timeRange[vm.index][vm.staticSettings.polKeyName])); + let polygonLatLngsRaw = mapPolygonArray(angular.fromJson(calculateCurrentDate(trip.timeRange, vm.index)[vm.staticSettings.polKeyName])); trip.polygon = L.polygon(polygonLatLngsRaw, polygonSettings).addTo(vm.map.map); trip.polygon.on('click',function(){showHidePolygonTooltip(trip)}); } @@ -912,6 +864,35 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } } + function calculateCurrentDate(tripTimeRange, index) { + let time = vm.minTime + index * vm.staticSettings.normalizationStep; + if (Object.hasOwnProperty.call(tripTimeRange, time)) { + return tripTimeRange[time]; + } else { + let timeInterval = Object.keys(tripTimeRange); + for (let i = 1; i < timeInterval.length; i++) { + if (timeInterval[i - 1] < time && timeInterval[i] > time) { + let calcPosition = angular.copy(tripTimeRange[timeInterval[i - 1]]); + let startLatLng = tripTimeRange[timeInterval[i - 1]].latLng; + let finishLatLng = tripTimeRange[timeInterval[i]].latLng; + let percentRouteComplete = (time - timeInterval[i - 1]) / (timeInterval[i] - timeInterval[i - 1]); + calcPosition[vm.staticSettings.latKeyName] = startLatLng.lat + (finishLatLng.lat - startLatLng.lat) * percentRouteComplete; + calcPosition[vm.staticSettings.lngKeyName] = startLatLng.lng + (finishLatLng.lng - startLatLng.lng) * percentRouteComplete; + calcPosition.latLng = vm.map.createLatLng(calcPosition[vm.staticSettings.latKeyName], calcPosition[vm.staticSettings.lngKeyName]); + calcPosition.angle = vm.staticSettings.rotationAngle + findAngle(startLatLng, finishLatLng); + return calcPosition; + } + } + } + return {}; + } + + function findAngle(startPoint, endPoint) { + let angle = -Math.atan2(endPoint.lat - startPoint.lat, endPoint.lng - startPoint.lng); + angle = angle * 180 / Math.PI; + return parseInt(angle.toFixed(2)); + } + function mapPolygonArray(rawArray) { return rawArray.map(function (el) { if (el.length === 2) { @@ -931,15 +912,16 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt } function moveMarker(trip) { - if (angular.isDefined(trip.timeRange[vm.index].latLng)) { + let postionMarker = calculateCurrentDate(trip.timeRange, vm.index); + if (angular.isDefined(postionMarker)) { if (angular.isDefined(trip.marker)) { trip.markerAngleIsSet = true; - trip.marker.setLatLng(trip.timeRange[vm.index].latLng); - trip.marker.setRotationAngle(trip.timeRange[vm.index].h); + trip.marker.setLatLng(postionMarker.latLng); + trip.marker.setRotationAngle(postionMarker.angle); trip.marker.update(); } else { if (trip.timeRange && trip.timeRange.length) { - trip.marker = L.marker(trip.timeRange[vm.index].latLng); + trip.marker = L.marker(postionMarker.latLng); trip.marker.setZIndexOffset(1000); trip.marker.setIcon(vm.staticSettings.icon); trip.marker.setRotationOrigin('center center'); @@ -1000,4 +982,4 @@ function tripAnimationController($document, $scope, $log, $http, $timeout, $filt if (trip && vm.activeTripIndex !== trip.dSIndex) vm.activeTripIndex = trip.dSIndex; vm.mainTooltip = vm.trips[vm.activeTripIndex].settings.polygonTooltipText; } -} \ No newline at end of file +} diff --git a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html index 49c19b96d9..9bd1cb35ba 100644 --- a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html +++ b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html @@ -27,8 +27,10 @@ -
+
@@ -39,7 +41,7 @@ skip_previous - + skip_next @@ -61,7 +63,9 @@ -
{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}} +
+ {{ vm.animationTime | date:'medium'}} + {{ "widget.no-data" | translate}}
From a51c44e5dbc90b4975ded1a9f73a4560d120a507 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 8 Nov 2019 14:20:21 +0200 Subject: [PATCH 037/120] Multiple attributes input widget improvement (#2144) * Extend multiple input widget * Update multiple attributes widget * Change bundle, minor changes * Fix disablefor date input --- .../system/widget_bundles/input_widgets.json | 6 +- .../app/widget/lib/multiple-input-widget.js | 306 ++++++++---------- .../app/widget/lib/multiple-input-widget.scss | 76 ++++- .../widget/lib/multiple-input-widget.tpl.html | 155 ++++++--- 4 files changed, 326 insertions(+), 217 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 51717b12ec..7328dc7fb3 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -319,9 +319,9 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Multiple input title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"attributesShared\": {\n \"title\": \"Attributes are 'shared' (default value is 'server')\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"attributesShared\",\n \"showResultMessage\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"readOnly\": {\n \"title\": \"Value is read only\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"inputTypeNumber\": {\n \"title\": \"Datakey is a number\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between valid values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"readOnly\",\n \"inputTypeNumber\",\n \"step\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t},\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-resize', self.ctx.$scope.formId);\r\n}\r\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"showResultMessage\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n \"required\",\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index ad8972ff7d..c68c22704e 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -41,25 +41,18 @@ function MultipleInputWidget() { } /*@ngInject*/ -function MultipleInputWidgetController($q, $scope, attributeService, toast, types, utils) { +function MultipleInputWidgetController($q, $scope, $translate, attributeService, toast, types, utils) { var vm = this; - vm.dataKeyDetected = false; - vm.hasAnyChange = false; vm.entityDetected = false; - vm.isValidParameter = true; - vm.message = 'No entity selected'; - - vm.rows = []; - vm.rowIndex = 0; + vm.isAllParametersValid = true; + vm.data = []; vm.datasources = null; - vm.cellStyle = cellStyle; - vm.textColor = textColor; vm.discardAll = discardAll; vm.inputChanged = inputChanged; - vm.postData = postData; + vm.save = save; $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { @@ -74,127 +67,109 @@ function MultipleInputWidgetController($q, $scope, attributeService, toast, type $scope.$on('multiple-input-data-updated', function(event, formId) { if (vm.formId == formId) { - updateRowData(vm.subscription.data); + updateWidgetData(vm.subscription.data); $scope.$digest(); } }); - function defaultStyle() { - return {}; - } - - function cellStyle(key, rowIndex, firstKey, lastKey) { - var style = {}; - if (key) { - var styleInfo = vm.stylesInfo[key.label]; - var value = key.currentValue; - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { - try { - style = styleInfo.cellStyleFunction(value); - } catch (e) { - style = {}; - } - } else { - style = defaultStyle(); - } - } - if (vm.settings.rowMargin) { - if (angular.isUndefined(style.marginTop) && rowIndex != 0) { - style.marginTop = (vm.settings.rowMargin / 2) + 'px'; - } - if (angular.isUndefined(style.marginBottom)) { - style.marginBottom = (vm.settings.rowMargin / 2) + 'px'; - } - } - if (vm.settings.columnMargin) { - if (angular.isUndefined(style.marginLeft) && !firstKey) { - style.marginLeft = (vm.settings.columnMargin / 2) + 'px'; - } - if (angular.isUndefined(style.marginRight) && !lastKey) { - style.marginRight = (vm.settings.columnMargin / 2) + 'px'; - } - } - return style; - } - - function textColor(key) { - var style = {}; - if (key) { - var styleInfo = vm.stylesInfo[key.label]; - if (styleInfo.color) { - style = { color: styleInfo.color }; - } + $scope.$on('multiple-input-resize', function(event, formId) { + if (vm.formId == formId) { + updateWidgetDisplaying(); } - return style; - } + }); function discardAll() { - for (var r = 0; r < vm.rows.length; r++) { - var row = vm.rows[r]; - for (var d = 0; d < row.data.length; d++ ) { - row.data[d].currentValue = row.data[d].originalValue; - } + for (var i = 0; i < vm.data.length; i++) { + vm.data[i].data.currentValue = vm.data[i].data.originalValue; } - vm.hasAnyChange = false; + $scope.multipleInputForm.$setPristine(); } - function inputChanged() { - var newValue = false; - for (var r = 0; r < vm.rows.length; r++) { - var row = vm.rows[r]; - for (var d = 0; d < row.data.length; d++ ) { - if (!row.data[d].currentValue) { - return; - } - if (row.data[d].currentValue !== row.data[d].originalValue) { - newValue = true; - } + function inputChanged(key) { + if (!vm.settings.showActionButtons) { + if (!key.settings.required || (key.settings.required && key.data && angular.isDefined(key.data.currentValue))) { + vm.save(key); } } - vm.hasAnyChange = newValue; } - function postData() { - var promises = []; - for (var r = 0; r < vm.rows.length; r++) { - var row = vm.rows[r]; - var datasource = row.datasource; - var attributes = []; - var newValues = false; - - for (var d = 0; d < row.data.length; d++ ) { - if (row.data[d].currentValue !== row.data[d].originalValue) { - attributes.push({ - key : row.data[d].name, - value : row.data[d].currentValue, - }); - newValues = true; + function save(key) { + var tasks = []; + var serverAttributes = [], sharedAttributes = [], telemetry = []; + var config = { + ignoreLoading: !vm.settings.showActionButtons + }; + var data; + if (key) { + data = [key]; + } else { + data = vm.data; + } + for (let i = 0; i < data.length; i++) { + var item = data[i]; + if (item.data.currentValue !== item.data.originalValue) { + var attribute = { + key: item.name + }; + switch (item.settings.dataKeyValueType) { + case 'dateTime': + case 'date': + attribute.value = item.data.currentValue.getTime(); + break; + case 'time': + attribute.value = item.data.currentValue.getTime() - moment().startOf('day').valueOf();//eslint-disable-line + break; + default: + attribute.value = item.data.currentValue; } - } - if (newValues) { - promises.push(attributeService.saveEntityAttributes( - datasource.entityType, - datasource.entityId, - vm.attributeScope, - attributes)); + switch (item.settings.dataKeyType) { + case 'shared': + sharedAttributes.push(attribute); + break; + case 'timeseries': + telemetry.push(attribute); + break; + default: + serverAttributes.push(attribute); + } } } - - if (promises.length) { - $q.all(promises).then( + for (let i = 0; i < serverAttributes.length; i++) { + tasks.push(attributeService.saveEntityAttributes( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.attributesScope.server.value, + serverAttributes, + config)); + } + for (let i = 0; i < sharedAttributes.length; i++) { + tasks.push(attributeService.saveEntityAttributes( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.attributesScope.shared.value, + sharedAttributes, + config)); + } + for (let i = 0; i < telemetry.length; i++) { + tasks.push(attributeService.saveEntityTimeseries( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.latestTelemetry.value, + telemetry, + config)); + } + if (tasks.length) { + $q.all(tasks).then( function success() { - for (var d = 0; d < row.data.length; d++ ) { - row.data[d].originalValue = row.data[d].currentValue; - } - vm.hasAnyChange = false; + $scope.multipleInputForm.$setPristine(); if (vm.settings.showResultMessage) { - toast.showSuccess('Update successful', 1000, angular.element(vm.ctx.$container), 'bottom left'); + toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(vm.ctx.$container), 'bottom left'); } }, function fail() { if (vm.settings.showResultMessage) { - toast.showError('Update failed', angular.element(vm.ctx.$container), 'bottom left'); + toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(vm.ctx.$container), 'bottom left'); } } ); @@ -211,78 +186,75 @@ function MultipleInputWidgetController($q, $scope, attributeService, toast, type vm.ctx.widgetTitle = vm.widgetTitle; - vm.attributeScope = vm.settings.attributesShared ? types.attributesScope.shared.value : types.attributesScope.server.value; + vm.isVerticalAlignment = !(vm.settings.fieldsAlignment === 'row'); + + if (!vm.isVerticalAlignment && vm.settings.fieldsInRow) { + vm.inputWidthSettings = 100 / vm.settings.fieldsInRow + '%'; + } } function updateDatasources() { + if (vm.datasources && vm.datasources.length) { + var datasource = vm.datasources[0]; + if (datasource.type === types.datasourceType.entity) { + for (var i = 0; i < datasource.dataKeys.length; i++) { + if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[i].settings.dataKeyType !== 'server')) { + vm.isAllParametersValid = false; + } + vm.data.push(datasource.dataKeys[i]); + vm.data[i].data = {}; + } + vm.entityDetected = true; + } + } + } - vm.stylesInfo = {}; - vm.rows = []; - vm.rowIndex = 0; - - if (vm.datasources) { - vm.entityDetected = true; - for (var ds = 0; ds < vm.datasources.length; ds++) { - var row = {}; - var datasource = vm.datasources[ds]; - row.datasource = datasource; - row.data = []; - if (datasource.dataKeys) { - vm.dataKeyDetected = true; - for (var a = 0; a < datasource.dataKeys.length; a++ ) { - var dataKey = datasource.dataKeys[a]; - - if (dataKey.units) { - dataKey.label += ' (' + dataKey.units + ')'; - } - - var keySettings = dataKey.settings; - if (keySettings.inputTypeNumber) { - keySettings.inputType = 'number'; - } else { - keySettings.inputType = 'text'; - } + function updateWidgetData(data) { + for (var i = 0; i < vm.data.length; i++) { + var keyData = data[i].data; + if (keyData && keyData.length) { + var value; + switch (vm.data[i].settings.dataKeyValueType) { + case 'dateTime': + case 'date': + value = moment(keyData[0][1]).toDate(); // eslint-disable-line + break; + case 'time': + value = moment().startOf('day').add(keyData[0][1], 'ms').toDate(); // eslint-disable-line + break; + case 'booleanCheckbox': + case 'booleanSwitch': + value = (keyData[0][1] === 'true'); + break; + default: + value = keyData[0][1]; + } - var cellStyleFunction = null; - var useCellStyleFunction = false; + vm.data[i].data = { + currentValue: value, + originalValue: value + }; - if (keySettings.useCellStyleFunction === true) { - if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { - try { - cellStyleFunction = new Function('value', keySettings.cellStyleFunction); - useCellStyleFunction = true; - } catch (e) { - cellStyleFunction = null; - useCellStyleFunction = false; - } + if (vm.data[i].settings.isEditable === 'editable' && vm.data[i].settings.disabledOnDataKey) { + var conditions = data.filter((item) => { + return item.dataKey.name === vm.data[i].settings.disabledOnDataKey; + }); + if (conditions && conditions.length) { + if (conditions[0].data.length) { + if (conditions[0].data[0][1] === 'false') { + vm.data[i].settings.disabledOnCondition = true; + } else { + vm.data[i].settings.disabledOnCondition = !conditions[0].data[0][1]; } } - - vm.stylesInfo[dataKey.label] = { - useCellStyleFunction: useCellStyleFunction, - cellStyleFunction: cellStyleFunction, - color: keySettings.color - }; - - row.data.push(dataKey); } - vm.rows.push(row); } } } } - function updateRowData(data) { - var dataIndex = 0; - for (var r = 0; r < vm.rows.length; r++) { - var row = vm.rows[r]; - for (var d = 0; d < row.data.length; d++ ) { - var keyData = data[dataIndex++].data; - if (keyData && keyData.length && keyData[0].length > 1) { - row.data[d].currentValue = row.data[d].originalValue = keyData[0][1]; - } - } - } + function updateWidgetDisplaying() { + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth < 620); + vm.smallWidthContainer = (vm.ctx.$container[0].offsetWidth < 420); } - } diff --git a/ui/src/app/widget/lib/multiple-input-widget.scss b/ui/src/app/widget/lib/multiple-input-widget.scss index 7e16b3f967..ea5cf019fc 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.scss +++ b/ui/src/app/widget/lib/multiple-input-widget.scss @@ -15,15 +15,25 @@ */ .tb-multiple-input { height: 100%; + overflow-x: hidden; + overflow-y: auto; - .md-button.md-icon-button { - width: 32px; - min-width: 32px; - height: 32px; - min-height: 32px; - padding: 0 !important; - margin: 0; - line-height: 20px; + .input-field { + padding-right: 10px; + + md-input-container, + md-checkbox, + md-switch { + margin-bottom: 5px; + } + } + + md-checkbox { + margin-top: 22px; + } + + md-switch { + margin-top: 20px; } .md-icon-button md-icon { @@ -32,10 +42,56 @@ height: 20px; min-height: 20px; font-size: 20px; + } + + .date-time-input { + &__label { + margin-left: 36px; + font-size: 12px; + color: rgba(0, 0, 0, .54); + } + + mdp-date-picker, + mdp-time-picker { + width: 100%; + + .md-button.md-icon-button { + margin: 5px 0 0; + } - &:not([disabled]) { - color: #f66; + md-input-container { + width: 100%; + margin: 2px 0; + + label { + display: none; + } + } + } + } + + .vertical-alignment { + flex-direction: column; + + md-checkbox, + md-switch { + margin-top: 18px; } + + md-switch { + display: flex; + justify-content: space-between; + } + + .date-time-input { + &__label { + margin-top: 10px; + } + } + } + + .vertically-aligned { + flex-direction: column; } } diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index 1263919825..145b90a32d 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -15,53 +15,134 @@ limitations under the License. --> -
+
-
-
- - - - - - - {{key.settings.icon}} - - -
-
Value must be greater than {{key.settings.min}}
-
Value must be lower than {{key.settings.max}}
-
This field is required
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
value.invalid-integer-value
+
+
+
+
+ + {{key.label}} + +
+
+ + {{key.label}} + +
+
+ +
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
- +
-
-
- No attribute is selected +
+ {{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
- Timeseries parameter cannot be used in this widget +
+ {{ 'widgets.input-widgets.not-allowed-entity' | translate }}
-
- +
+ {{ 'action.undo' | translate }} - + {{ 'action.save' | translate }}
From fc7818641412fa665072698c2f0f12895eadb193 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Fri, 8 Nov 2019 14:21:21 +0200 Subject: [PATCH 038/120] fix the right message sending on transaction end (#2137) --- .../BaseRuleChainTransactionService.java | 42 +++++-------------- application/src/main/proto/cluster.proto | 8 ---- 2 files changed, 11 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java b/application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java index 432d0b2bc6..b40e2b93fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java +++ b/application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java @@ -15,14 +15,12 @@ */ package org.thingsboard.server.service.transaction; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.RuleChainTransactionService; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; @@ -34,7 +32,6 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Optional; import java.util.Queue; -import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -111,29 +108,18 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ @Override public void endTransaction(TbMsg msg, Consumer onSuccess, Consumer onFailure) { - EntityId originatorId = msg.getTransactionData().getOriginatorId(); - UUID transactionId = msg.getTransactionData().getTransactionId(); - - Optional address = routingService.resolveById(originatorId); + Optional address = routingService.resolveById(msg.getTransactionData().getOriginatorId()); if (address.isPresent()) { - sendTransactionEventToRemoteServer(originatorId, transactionId, address.get()); + sendTransactionEventToRemoteServer(msg, address.get()); executeOnSuccess(onSuccess, msg); } else { - endLocalTransaction(transactionId, originatorId, onSuccess, onFailure); + endLocalTransaction(msg, onSuccess, onFailure); } } @Override public void onRemoteTransactionMsg(ServerAddress serverAddress, byte[] data) { - ClusterAPIProtos.TransactionEndServiceMsgProto proto; - try { - proto = ClusterAPIProtos.TransactionEndServiceMsgProto.parseFrom(data); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - EntityId originatorId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getOriginatorIdMSB(), proto.getOriginatorIdLSB())); - UUID transactionId = new UUID(proto.getTransactionIdMSB(), proto.getTransactionIdLSB()); - endLocalTransaction(transactionId, originatorId, msg -> { + endLocalTransaction(TbMsg.fromBytes(data), msg -> { }, error -> { }); } @@ -144,21 +130,21 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ log.trace("Added msg to queue, size: [{}]", queue.size()); } - private void endLocalTransaction(UUID transactionId, EntityId originatorId, Consumer onSuccess, Consumer onFailure) { + private void endLocalTransaction(TbMsg msg, Consumer onSuccess, Consumer onFailure) { transactionLock.lock(); try { - BlockingQueue queue = transactionMap.computeIfAbsent(originatorId, id -> + BlockingQueue queue = transactionMap.computeIfAbsent(msg.getTransactionData().getOriginatorId(), id -> new LinkedBlockingQueue<>(finalQueueSize)); TbTransactionTask currentTransactionTask = queue.peek(); if (currentTransactionTask != null) { - if (currentTransactionTask.getMsg().getTransactionData().getTransactionId().equals(transactionId)) { + if (currentTransactionTask.getMsg().getTransactionData().getTransactionId().equals(msg.getTransactionData().getTransactionId())) { currentTransactionTask.setCompleted(true); queue.poll(); log.trace("Removed msg from queue, size [{}]", queue.size()); executeOnSuccess(currentTransactionTask.getOnEnd(), currentTransactionTask.getMsg()); - executeOnSuccess(onSuccess, currentTransactionTask.getMsg()); + executeOnSuccess(onSuccess, msg); TbTransactionTask nextTransactionTask = queue.peek(); if (nextTransactionTask != null) { @@ -247,14 +233,8 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ callbackExecutor.executeAsync(task); } - private void sendTransactionEventToRemoteServer(EntityId entityId, UUID transactionId, ServerAddress address) { - log.trace("[{}][{}] Originator is monitored on other server: {}", entityId, transactionId, address); - ClusterAPIProtos.TransactionEndServiceMsgProto.Builder builder = ClusterAPIProtos.TransactionEndServiceMsgProto.newBuilder(); - builder.setEntityType(entityId.getEntityType().name()); - builder.setOriginatorIdMSB(entityId.getId().getMostSignificantBits()); - builder.setOriginatorIdLSB(entityId.getId().getLeastSignificantBits()); - builder.setTransactionIdMSB(transactionId.getMostSignificantBits()); - builder.setTransactionIdLSB(transactionId.getLeastSignificantBits()); - clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TRANSACTION_SERVICE_MESSAGE, builder.build().toByteArray()); + private void sendTransactionEventToRemoteServer(TbMsg msg, ServerAddress address) { + log.trace("[{}][{}] Originator is monitored on other server: {}", msg.getTransactionData().getOriginatorId(), msg.getTransactionData().getTransactionId(), address); + clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TRANSACTION_SERVICE_MESSAGE, TbMsg.toByteArray(msg)); } } diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto index b04a95fbb0..4ff1359e76 100644 --- a/application/src/main/proto/cluster.proto +++ b/application/src/main/proto/cluster.proto @@ -143,11 +143,3 @@ message DeviceStateServiceMsgProto { bool updated = 6; bool deleted = 7; } - -message TransactionEndServiceMsgProto { - string entityType = 1; - int64 originatorIdMSB = 2; - int64 originatorIdLSB = 3; - int64 transactionIdMSB = 4; - int64 transactionIdLSB = 5; -} From 5d83d6c21c5ddcd0dbf170417c835d4455ff3093 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Fri, 8 Nov 2019 14:21:55 +0200 Subject: [PATCH 039/120] Fix not work in the autocomplete tooltip action create datakey (#2139) --- ui/src/app/components/datasource-entity.directive.js | 6 +++++- ui/src/app/components/datasource-entity.tpl.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js index afde04e2d2..9ed05b8812 100644 --- a/ui/src/app/components/datasource-entity.directive.js +++ b/ui/src/app/components/datasource-entity.directive.js @@ -165,7 +165,11 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc }; scope.transformAlarmDataKeyChip = function (chip) { - return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); + if (chip.type) { + return scope.generateDataKey({chip: chip.name, type: chip.type}); + } else { + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); + } }; scope.showColorPicker = function (event, dataKey) { diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index 9a58ecdb66..fe0d23eef7 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -125,7 +125,7 @@
entity.no-key-matching - entity.create-new-key + entity.create-new-key
From 695d3a796cd47f99f4db3396b9307ec6840f3122 Mon Sep 17 00:00:00 2001 From: woodyjon <30724004+woodyjon@users.noreply.github.com> Date: Fri, 8 Nov 2019 14:42:10 +0100 Subject: [PATCH 040/120] 2 small typos in widget codes (#2160) --- .../src/main/data/json/system/widget_bundles/cards.json | 2 +- .../data/json/system/widget_bundles/control_widgets.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index db004ff87f..bc06678759 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -15,7 +15,7 @@ "resources": [], "templateHtml": "", "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", - "controllerScript": "self.onInit = function() {\n \n self.ctx.datasourceTitleCells = [];\n self.ctx.valueCells = [];\n self.ctx.labelCells = [];\n \n for (var i=0; i < self.ctx.datasources.length; i++) {\n var tbDatasource = self.ctx.datasources[i];\n\n var datasourceId = 'tbDatasource' + i;\n self.ctx.$container.append(\n \"
\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"
\" +\n tbDatasource.name + \"
\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"
\"\n );\n var table = $('#' + tableId, self.ctx.$container);\n\n for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n var dataKey = tbDatasource.dataKeys[a];\n var labelCellId = 'labelCell' + a;\n var cellId = 'cell' + a;\n table.append(\"\" + dataKey.label +\n \"\");\n var labelCell = $('#' + labelCellId, table);\n self.ctx.labelCells.push(labelCell);\n var valueCell = $('#' + cellId, table);\n self.ctx.valueCells.push(valueCell);\n }\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.valueCells.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData && cellData.data && cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length -\n 1];\n var value = tvPair[1];\n var textValue;\n //toDo -> + IsNumber\n \n if (isNumber(value)) {\n var decimals = self.ctx.decimals;\n var units = self.ctx.units;\n if (cellData.dataKey.decimals || cellData.dataKey.decimals === 0) {\n decimals = cellData.dataKey.decimals;\n }\n if (cellData.dataKey.units) {\n units = cellData.dataKey.units;\n }\n txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n } else {\n txtValue = value;\n }\n self.ctx.valueCells[i].html(txtValue);\n }\n }\n \n function isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n}\n\nself.onResize = function() {\n var datasoirceTitleFontSize = self.ctx.height/8;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n datasoirceTitleFontSize = self.ctx.width/12;\n }\n datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n }\n var valueFontSize = self.ctx.height/9;\n var labelFontSize = self.ctx.height/9;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n valueFontSize = self.ctx.width/15;\n labelFontSize = self.ctx.width/15;\n }\n valueFontSize = Math.min(valueFontSize, 18);\n labelFontSize = Math.min(labelFontSize, 18);\n\n for (i = 0; i < self.ctx.valueCells; i++) {\n self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n } \n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "self.onInit = function() {\n \n self.ctx.datasourceTitleCells = [];\n self.ctx.valueCells = [];\n self.ctx.labelCells = [];\n \n for (var i=0; i < self.ctx.datasources.length; i++) {\n var tbDatasource = self.ctx.datasources[i];\n\n var datasourceId = 'tbDatasource' + i;\n self.ctx.$container.append(\n \"
\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"
\" +\n tbDatasource.name + \"
\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"
\"\n );\n var table = $('#' + tableId, self.ctx.$container);\n\n for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n var dataKey = tbDatasource.dataKeys[a];\n var labelCellId = 'labelCell' + a;\n var cellId = 'cell' + a;\n table.append(\"\" + dataKey.label +\n \"\");\n var labelCell = $('#' + labelCellId, table);\n self.ctx.labelCells.push(labelCell);\n var valueCell = $('#' + cellId, table);\n self.ctx.valueCells.push(valueCell);\n }\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.valueCells.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData && cellData.data && cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length -\n 1];\n var value = tvPair[1];\n var textValue;\n //toDo -> + IsNumber\n \n if (isNumber(value)) {\n var decimals = self.ctx.decimals;\n var units = self.ctx.units;\n if (cellData.dataKey.decimals || cellData.dataKey.decimals === 0) {\n decimals = cellData.dataKey.decimals;\n }\n if (cellData.dataKey.units) {\n units = cellData.dataKey.units;\n }\n txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n } else {\n txtValue = value;\n }\n self.ctx.valueCells[i].html(txtValue);\n }\n }\n \n function isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n}\n\nself.onResize = function() {\n var datasourceTitleFontSize = self.ctx.height/8;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n datasourceTitleFontSize = self.ctx.width/12;\n }\n datasourceTitleFontSize = Math.min(datasourceTitleFontSize, 20);\n for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n self.ctx.datasourceTitleCells[i].css('font-size', datasourceTitleFontSize+'px');\n }\n var valueFontSize = self.ctx.height/9;\n var labelFontSize = self.ctx.height/9;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n valueFontSize = self.ctx.width/15;\n labelFontSize = self.ctx.width/15;\n }\n valueFontSize = Math.min(valueFontSize, 18);\n labelFontSize = Math.min(labelFontSize, 18);\n\n for (i = 0; i < self.ctx.valueCells; i++) {\n self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n } \n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Attributes card\"}" diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 5dfe4d0f7f..44f7d52ebf 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -109,9 +109,9 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n {{buttonLable}}\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n {{buttonLabel}}\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n }\n );\n };\n\n};", + "controllerScript": "self.onInit = function() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLabel = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n }\n );\n };\n\n};", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -125,9 +125,9 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n {{buttonLable}}\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n {{buttonLabel}}\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n console.log(self.ctx);\n\n let attributeService = self.ctx.$scope.$injector.get('attributeService');\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n \n attributeService.saveEntityAttributes(\"DEVICE\", self.ctx.defaultSubscription.targetDeviceId,\n entityAttributeType, attributes).then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n console.log(rejection);\n }\n\n );\n };\n\n};", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.buttonLabel = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n console.log(self.ctx);\n\n let attributeService = self.ctx.$scope.$injector.get('attributeService');\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n \n attributeService.saveEntityAttributes(\"DEVICE\", self.ctx.defaultSubscription.targetDeviceId,\n entityAttributeType, attributes).then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n console.log(rejection);\n }\n\n );\n };\n\n};", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Update device attribute\"\n },\n \"entityAttributeType\": {\n \"title\": \"Device attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"entityParameters\": {\n \"title\": \"Device attribute parameters\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n {\n \"key\": \"entityAttributeType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n }, {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }]\n }, {\n \"key\": \"entityParameters\",\n \"type\": \"json\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"entityParameters\":\"{}\",\"entityAttributeType\":\"SERVER_SCOPE\",\"buttonText\":\"Update device attribute\"},\"title\":\"Update device attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"targetDeviceAliases\":[]}" From a888c97033f30de9bc241e33f5b734a1b28c0143 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Fri, 8 Nov 2019 17:44:21 +0200 Subject: [PATCH 041/120] Fix translate (#2163) --- ui/package-lock.json | 28 +++++-------------- .../trip-animation-widget.tpl.html | 2 +- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index ced5230b8c..c8c8df6fdc 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6096,14 +6096,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6118,20 +6116,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -6248,8 +6243,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -6261,7 +6255,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6276,7 +6269,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6284,14 +6276,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6310,7 +6300,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6391,8 +6380,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6404,7 +6392,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6526,7 +6513,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html index 9bd1cb35ba..ce2d6e324d 100644 --- a/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html +++ b/ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html @@ -65,7 +65,7 @@
{{ vm.animationTime | date:'medium'}} - {{ "widget.no-data" | translate}} + {{ "widget.no-data-found" | translate}}
From 6f2c1deca87f13c32d2dd60e446b5cc4e6dd4098 Mon Sep 17 00:00:00 2001 From: nordmif Date: Mon, 11 Nov 2019 17:42:29 +0200 Subject: [PATCH 042/120] increased default JS execution time to 3000 ms --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 5c55dc4249..e716087f77 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -434,7 +434,7 @@ js: # Specify thread pool size for JavaScript sandbox resource monitor monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" # Maximum CPU time in milliseconds allowed for script execution - max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:100}" + max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}" # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}" # Remote JavaScript environment properties From 25a6927b6e0987838c98a62acda967d671a1affc Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Tue, 12 Nov 2019 13:31:45 +0200 Subject: [PATCH 043/120] Fix disable on condition and errors displaying --- .../app/widget/lib/multiple-input-widget.js | 24 +++++++++--------- .../app/widget/lib/multiple-input-widget.scss | 1 + .../widget/lib/multiple-input-widget.tpl.html | 25 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index c68c22704e..1993a01aa4 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -198,7 +198,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, var datasource = vm.datasources[0]; if (datasource.type === types.datasourceType.entity) { for (var i = 0; i < datasource.dataKeys.length; i++) { - if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[i].settings.dataKeyType !== 'server')) { + if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[i].settings.dataKeyType == 'shared')) { vm.isAllParametersValid = false; } vm.data.push(datasource.dataKeys[i]); @@ -234,18 +234,18 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, currentValue: value, originalValue: value }; + } - if (vm.data[i].settings.isEditable === 'editable' && vm.data[i].settings.disabledOnDataKey) { - var conditions = data.filter((item) => { - return item.dataKey.name === vm.data[i].settings.disabledOnDataKey; - }); - if (conditions && conditions.length) { - if (conditions[0].data.length) { - if (conditions[0].data[0][1] === 'false') { - vm.data[i].settings.disabledOnCondition = true; - } else { - vm.data[i].settings.disabledOnCondition = !conditions[0].data[0][1]; - } + if (vm.data[i].settings.isEditable === 'editable' && vm.data[i].settings.disabledOnDataKey) { + var conditions = data.filter((item) => { + return item.dataKey.name === vm.data[i].settings.disabledOnDataKey; + }); + if (conditions && conditions.length) { + if (conditions[0].data.length) { + if (conditions[0].data[0][1] === 'false') { + vm.data[i].settings.disabledOnCondition = true; + } else { + vm.data[i].settings.disabledOnCondition = !conditions[0].data[0][1]; } } } diff --git a/ui/src/app/widget/lib/multiple-input-widget.scss b/ui/src/app/widget/lib/multiple-input-widget.scss index ea5cf019fc..fdeafae93b 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.scss +++ b/ui/src/app/widget/lib/multiple-input-widget.scss @@ -34,6 +34,7 @@ md-switch { margin-top: 20px; + white-space: normal; } .md-icon-button md-icon { diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index 145b90a32d..e375af49c0 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -16,8 +16,8 @@ --> -
-
+
+
@@ -129,7 +129,17 @@
- +
+ + {{ 'action.undo' | translate }} + + + {{ 'action.save' | translate }} + +
+
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
@@ -137,13 +147,4 @@ {{ 'widgets.input-widgets.not-allowed-entity' | translate }}
-
- - {{ 'action.undo' | translate }} - - - {{ 'action.save' | translate }} - -
From 27775ca39e210c08ca47b8c3158215fd13c5e4b3 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 11 Nov 2019 18:22:22 +0200 Subject: [PATCH 044/120] Fix delete timeseries data --- ui/src/app/api/attribute.service.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/src/app/api/attribute.service.js b/ui/src/app/api/attribute.service.js index 6ed2921824..6ad9e62718 100644 --- a/ui/src/app/api/attribute.service.js +++ b/ui/src/app/api/attribute.service.js @@ -277,7 +277,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) } var deleteEntityTimeseriesPromise; if (deleteTimeseries.length) { - deleteEntityTimeseriesPromise = deleteEntityTimeseries(entityType, entityId, deleteTimeseries, config); + deleteEntityTimeseriesPromise = deleteEntityTimeseries(entityType, entityId, deleteTimeseries, config, true); } if (Object.keys(timeseriesData).length) { var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/' + timeseriesScope; @@ -331,8 +331,9 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) return deferred.promise; } - function deleteEntityTimeseries(entityType, entityId, timeseries, config) { + function deleteEntityTimeseries(entityType, entityId, timeseries, config, deleteAllDataForKeys) { config = config || {}; + deleteAllDataForKeys = deleteAllDataForKeys || false; var deferred = $q.defer(); var keys = ''; for (var i = 0; i < timeseries.length; i++) { @@ -341,7 +342,9 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) } keys += timeseries[i].key; } - var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + '?keys=' + keys; + var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + + '?keys=' + keys + + '&deleteAllDataForKeys=' + deleteAllDataForKeys; $http.delete(url, config).then(function success() { deferred.resolve(); }, function fail() { @@ -350,4 +353,4 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) return deferred.promise; } -} \ No newline at end of file +} From 8e18d7c0996a244d453edb3af97b8c7e3ba5f799 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 11 Nov 2019 18:25:22 +0200 Subject: [PATCH 045/120] Refactoring --- ui/src/app/api/attribute.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/api/attribute.service.js b/ui/src/app/api/attribute.service.js index 6ad9e62718..17acde4a62 100644 --- a/ui/src/app/api/attribute.service.js +++ b/ui/src/app/api/attribute.service.js @@ -343,8 +343,8 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) keys += timeseries[i].key; } var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + - '?keys=' + keys - + '&deleteAllDataForKeys=' + deleteAllDataForKeys; + '?keys=' + keys + + '&deleteAllDataForKeys=' + deleteAllDataForKeys; $http.delete(url, config).then(function success() { deferred.resolve(); }, function fail() { From 6af9aa0ecb1d891800b5e9b5f0549ed26b6490dc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 15 Nov 2019 18:27:57 +0200 Subject: [PATCH 046/120] JPA improvements. --- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../ClusterRpcCallbackExecutorService.java | 1 + .../executors/DbCallbackExecutorService.java | 1 + .../ExternalCallExecutorService.java | 1 + .../service/mail/MailExecutorService.java | 2 +- .../service/script/JsExecutorService.java | 2 +- .../src/main/resources/thingsboard.yml | 3 +- .../util}/AbstractListeningExecutor.java | 4 +-- .../common/util}/ListeningExecutor.java | 6 +++- dao/pom.xml | 4 +++ ...paAbstractDaoListeningExecutorService.java | 8 ++--- .../server/dao/sql/JpaExecutorService.java | 35 +++++++++++++++++++ .../server/dao/sql/audit/JpaAuditLogDao.java | 17 +++------ .../dao/sqlts/AbstractSqlTimeseriesDao.java | 4 +-- rule-engine/rule-engine-api/pom.xml | 7 +++- .../rule/engine/api/TbContext.java | 1 + .../rule/engine/action/TbLogNode.java | 1 + .../rule/engine/filter/TbJsFilterNode.java | 1 + .../rule/engine/filter/TbJsSwitchNode.java | 1 + .../rule/engine/action/TbAlarmNodeTest.java | 3 +- .../engine/filter/TbJsFilterNodeTest.java | 3 +- .../engine/filter/TbJsSwitchNodeTest.java | 3 +- .../transform/TbChangeOriginatorNodeTest.java | 4 +-- .../transform/TbTransformMsgNodeTest.java | 3 +- 24 files changed, 84 insertions(+), 33 deletions(-) rename {application/src/main/java/org/thingsboard/server/service/executors => common/util/src/main/java/org/thingsboard/common/util}/AbstractListeningExecutor.java (94%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/util/src/main/java/org/thingsboard/common/util}/ListeningExecutor.java (85%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/JpaExecutorService.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 669257dcfd..ae60262817 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.StringUtils; -import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleChainTransactionService; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java index 82604b63a3..1a4c654ea3 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.executors; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class ClusterRpcCallbackExecutorService extends AbstractListeningExecutor { diff --git a/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java index 40b4302d16..2c8678fd45 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.executors; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class DbCallbackExecutorService extends AbstractListeningExecutor { diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java index 200cfef090..f61e0bd450 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.executors; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class ExternalCallExecutorService extends AbstractListeningExecutor { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java b/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java index b56d884331..9a114dbba5 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.mail; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.server.service.executors.AbstractListeningExecutor; +import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class MailExecutorService extends AbstractListeningExecutor { diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java index 1252448b16..334e1b8772 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.script; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.server.service.executors.AbstractListeningExecutor; +import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class JsExecutorService extends AbstractListeningExecutor { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e716087f77..2d55e47ea0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -351,6 +351,7 @@ spring: repositories: enabled: "true" jpa: + open-in-view: "false" hibernate: ddl-auto: "none" database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" @@ -536,4 +537,4 @@ swagger: license: title: "${SWAGGER_LICENSE_TITLE:Apache License Version 2.0}" url: "${SWAGGER_LICENSE_URL:https://github.com/thingsboard/thingsboard/blob/master/LICENSE}" - version: "${SWAGGER_VERSION:2.0}" \ No newline at end of file + version: "${SWAGGER_VERSION:2.0}" diff --git a/application/src/main/java/org/thingsboard/server/service/executors/AbstractListeningExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/executors/AbstractListeningExecutor.java rename to common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java index 221915d02c..1f839edb80 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/AbstractListeningExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.executors; +package org.thingsboard.common.util; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.common.util.ListeningExecutor; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java similarity index 85% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java rename to common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java index 99df8f30fd..a12cc269b3 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.common.util; import com.google.common.util.concurrent.ListenableFuture; @@ -24,4 +24,8 @@ public interface ListeningExecutor extends Executor { ListenableFuture executeAsync(Callable task); + default ListenableFuture submit(Callable task) { + return executeAsync(task); + } + } diff --git a/dao/pom.xml b/dao/pom.xml index ba44133fb8..5e527a5aad 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -47,6 +47,10 @@ org.thingsboard.common dao-api + + org.thingsboard.common + util + org.slf4j slf4j-api diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java index 16368dff71..16723c024d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java @@ -17,16 +17,14 @@ package org.thingsboard.server.dao.sql; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PreDestroy; import java.util.concurrent.Executors; public abstract class JpaAbstractDaoListeningExecutorService { - protected ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); + @Autowired + protected JpaExecutorService service; - @PreDestroy - void onDestroy() { - service.shutdown(); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaExecutorService.java new file mode 100644 index 0000000000..998b8bb444 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaExecutorService.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; +import org.thingsboard.server.dao.util.SqlDao; + +@Component +@SqlDao +public class JpaExecutorService extends AbstractListeningExecutor { + + @Value("${spring.datasource.hikari.maximumPoolSize}") + private int poolSize; + + @Override + protected int getThreadPollSize() { + return poolSize; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java index a2c18c541b..9b6638b6cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -53,8 +53,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; @SqlDao public class JpaAuditLogDao extends JpaAbstractDao implements AuditLogDao { - private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - @Autowired private AuditLogRepository auditLogRepository; @@ -68,14 +66,9 @@ public class JpaAuditLogDao extends JpaAbstractDao imp return auditLogRepository; } - @PreDestroy - void onDestroy() { - insertService.shutdown(); - } - @Override public ListenableFuture saveByTenantId(AuditLog auditLog) { - return insertService.submit(() -> { + return service.submit(() -> { save(auditLog.getTenantId(), auditLog); return null; }); @@ -83,22 +76,22 @@ public class JpaAuditLogDao extends JpaAbstractDao imp @Override public ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog) { - return insertService.submit(() -> null); + return service.submit(() -> null); } @Override public ListenableFuture saveByTenantIdAndCustomerId(AuditLog auditLog) { - return insertService.submit(() -> null); + return service.submit(() -> null); } @Override public ListenableFuture saveByTenantIdAndUserId(AuditLog auditLog) { - return insertService.submit(() -> null); + return service.submit(() -> null); } @Override public ListenableFuture savePartitionsByTenantId(AuditLog auditLog) { - return insertService.submit(() -> null); + return service.submit(() -> null); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index cf3596a864..a7efad1e88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -70,7 +70,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx if (poolSize <= 0) { poolSize = maximumPoolSize * 4; } - insertService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(poolSize)); + insertService = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool(poolSize)); break; } } @@ -127,4 +127,4 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx Aggregation.NONE, DESC_ORDER); return findAllAsync(tenantId, entityId, findNewLatestQuery); } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 444b88cc8c..7253dcf483 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -48,6 +48,11 @@ dao-api provided + + org.thingsboard.common + util + provided + io.netty netty-all @@ -89,4 +94,4 @@ provided - \ No newline at end of file + diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 122715b2d4..a1902d3131 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api; import com.datastax.driver.core.ResultSetFuture; import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index 7a1578c0a1..a11a4bf18b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.action; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index afcf9dd80f..c23043bbb0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index d8e25387b9..0122b9fed7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index ea800f48bc..3402c89b44 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -28,6 +28,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.DeviceId; @@ -376,4 +377,4 @@ public class TbAlarmNodeTest { assertEquals(message, value.getMessage()); } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index f1aace48bb..49853abd9a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -26,6 +26,7 @@ import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -123,4 +124,4 @@ public class TbJsFilterNodeTest { assertEquals(expectedClass, value.getClass()); assertEquals(message, value.getMessage()); } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index 82ecbb4ba8..eca1a4a684 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -27,6 +27,7 @@ import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -105,4 +106,4 @@ public class TbJsSwitchNodeTest { assertEquals(expectedClass, value.getClass()); assertEquals(message, value.getMessage()); } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index feb561bb97..d31d184127 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -25,7 +25,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -166,4 +166,4 @@ public class TbChangeOriginatorNodeTest { node = new TbChangeOriginatorNode(); node.init(null, nodeConfiguration); } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 7f577255da..279864b951 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -26,6 +26,7 @@ import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -123,4 +124,4 @@ public class TbTransformMsgNodeTest { assertEquals(expectedClass, value.getClass()); assertEquals(message, value.getMessage()); } -} \ No newline at end of file +} From 33f703c2b8f42994b98af965bdeb272ced1cb689 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 19 Nov 2019 12:55:50 +0200 Subject: [PATCH 047/120] init commit --- .../dao/model/sql/AbsractTsKvEntity.java | 15 +--- .../sqlts/timescale/TimescaleTsKvEntity.java | 11 +++ .../server/dao/model/sqlts/ts/TsKvEntity.java | 12 ++- .../dao/model/sqlts/ts/TsKvLatestEntity.java | 53 ++---------- .../dao/sqlts/AbstractInsertRepository.java | 74 ++++++++++++++++ .../sqlts/AbstractLatestInsertRepository.java | 54 ++++++++++++ .../AbstractTimeseriesInsertRepository.java | 13 +-- .../timescale/TimescaleInsertRepository.java | 13 +-- .../sqlts/ts/HsqlLatestInsertRepository.java | 86 +++++++++++++++++++ .../ts/HsqlTimeseriesInsertRepository.java | 17 ++-- .../server/dao/sqlts/ts/JpaTimeseriesDao.java | 6 +- .../sqlts/ts/PsqlLatestInsertRepository.java | 86 +++++++++++++++++++ .../ts/PsqlTimeseriesInsertRepository.java | 17 ++-- 13 files changed, 353 insertions(+), 104 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java index 2773e5e4ab..d8c0e4ef0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java @@ -16,14 +16,11 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.ToData; import javax.persistence.Column; import javax.persistence.Id; @@ -35,11 +32,10 @@ import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Data @MappedSuperclass -public abstract class AbsractTsKvEntity implements ToData { +public abstract class AbsractTsKvEntity { protected static final String SUM = "SUM"; protected static final String AVG = "AVG"; @@ -50,10 +46,6 @@ public abstract class AbsractTsKvEntity implements ToData { @Column(name = ENTITY_ID_COLUMN) protected String entityId; - @Id - @Column(name = TS_COLUMN) - protected Long ts; - @Id @Column(name = KEY_COLUMN) protected String key; @@ -70,8 +62,7 @@ public abstract class AbsractTsKvEntity implements ToData { @Column(name = DOUBLE_VALUE_COLUMN) protected Double doubleValue; - @Override - public TsKvEntry toData() { + protected KvEntry getKvEntry() { KvEntry kvEntry = null; if (strValue != null) { kvEntry = new StringDataEntry(key, strValue); @@ -82,7 +73,7 @@ public abstract class AbsractTsKvEntity implements ToData { } else if (booleanValue != null) { kvEntry = new BooleanDataEntry(key, booleanValue); } - return new BasicTsKvEntry(ts, kvEntry); + return kvEntry; } public abstract boolean isNotEmpty(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java index fa212dd2f9..3427c928f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.model.sqlts.timescale; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; @@ -35,6 +36,7 @@ import javax.persistence.SqlResultSetMappings; import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG_QUERY; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_COUNT; @@ -119,6 +121,10 @@ public final class TimescaleTsKvEntity extends AbsractTsKvEntity implements ToDa @Column(name = TENANT_ID_COLUMN) private String tenantId; + @Id + @Column(name = TS_COLUMN) + protected Long ts; + public TimescaleTsKvEntity() { } public TimescaleTsKvEntity(Long tsBucket, Long interval, Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String strValue, String aggType) { @@ -181,4 +187,9 @@ public final class TimescaleTsKvEntity extends AbsractTsKvEntity implements ToDa public boolean isNotEmpty() { return ts != null && (strValue != null || longValue != null || doubleValue != null || booleanValue != null); } + + @Override + public TsKvEntry toData() { + return new BasicTsKvEntry(ts, getKvEntry()); + } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index 4440a3dd0c..c5b9237f13 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.model.sqlts.ts; import lombok.Data; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; @@ -30,6 +31,7 @@ import javax.persistence.IdClass; import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Data @Entity @@ -42,6 +44,10 @@ public final class TsKvEntity extends AbsractTsKvEntity implements ToData { +public final class TsKvLatestEntity extends AbsractTsKvEntity implements ToData { - - //TODO: reafctor this and TsKvEntity to avoid code duplicates @Id @Enumerated(EnumType.STRING) @Column(name = ENTITY_TYPE_COLUMN) private EntityType entityType; - @Id - @Column(name = ENTITY_ID_COLUMN) - private String entityId; - - @Id - @Column(name = KEY_COLUMN) - private String key; - @Column(name = TS_COLUMN) private long ts; - @Column(name = BOOLEAN_VALUE_COLUMN) - private Boolean booleanValue; - - @Column(name = STRING_VALUE_COLUMN) - private String strValue; - - @Column(name = LONG_VALUE_COLUMN) - private Long longValue; - - @Column(name = DOUBLE_VALUE_COLUMN) - private Double doubleValue; - @Override public TsKvEntry toData() { - KvEntry kvEntry = null; - if (strValue != null) { - kvEntry = new StringDataEntry(key, strValue); - } else if (longValue != null) { - kvEntry = new LongDataEntry(key, longValue); - } else if (doubleValue != null) { - kvEntry = new DoubleDataEntry(key, doubleValue); - } else if (booleanValue != null) { - kvEntry = new BooleanDataEntry(key, booleanValue); - } - return new BasicTsKvEntry(ts, kvEntry); + return new BasicTsKvEntry(ts, getKvEntry()); + } + + @Override + public boolean isNotEmpty() { + return strValue != null || longValue != null || doubleValue != null || booleanValue != null; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java new file mode 100644 index 0000000000..bf647ada38 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sqlts; + +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@Repository +public abstract class AbstractInsertRepository { + + protected static final String BOOL_V = "bool_v"; + protected static final String STR_V = "str_v"; + protected static final String LONG_V = "long_v"; + protected static final String DBL_V = "dbl_v"; + + protected static final String TS_KV_LATEST_TABLE = "ts_kv_latest"; + protected static final String TS_KV_TABLE = "ts_kv"; + + protected static final String HSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_TABLE, BOOL_V); + protected static final String HSQL_ON_STR_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_TABLE, STR_V); + protected static final String HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_TABLE, LONG_V); + protected static final String HSQL_ON_DBL_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_TABLE, DBL_V); + + protected static final String HSQL_LATEST_ON_BOOL_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_LATEST_TABLE, BOOL_V); + protected static final String HSQL_LATEST_ON_STR_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_LATEST_TABLE, STR_V); + protected static final String HSQL_LATEST_ON_LONG_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_LATEST_TABLE, LONG_V); + protected static final String HSQL_LATEST_ON_DBL_VALUE_UPDATE_SET_NULLS = getHsqlNullValues(TS_KV_LATEST_TABLE, DBL_V); + + protected static final String PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, dbl_v = null"; + protected static final String PSQL_ON_STR_VALUE_UPDATE_SET_NULLS = "bool_v = null, long_v = null, dbl_v = null"; + protected static final String PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS = "str_v = null, bool_v = null, dbl_v = null"; + protected static final String PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, bool_v = null"; + + @PersistenceContext + protected EntityManager entityManager; + + protected static String getInsertOrUpdateStringHsql(String tableName, String constraint, String value, String nullValues) { + return "MERGE INTO " + tableName + " USING(VALUES :entity_type, :entity_id, :key, :ts, :" + value + ") A (entity_type, entity_id, key, ts, " + value + ") ON " + constraint + " WHEN MATCHED THEN UPDATE SET " + tableName + "." + value + " = A." + value + ", " + tableName + ".ts = A.ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, " + value + ") VALUES (A.entity_type, A.entity_id, A.key, A.ts, A." + value + ")"; + } + + protected static String getInsertOrUpdateStringPsql(String tableName, String constraint, String value, String nullValues) { + return "INSERT INTO " + tableName + " (entity_type, entity_id, key, ts, " + value + ") VALUES (:entity_type, :entity_id, :key, :ts, :" + value + ") ON CONFLICT " + constraint + " DO UPDATE SET " + value + " = :" + value + ", ts = :ts," + nullValues; + } + + private static String getHsqlNullValues(String tableName, String notNullValue) { + switch (notNullValue) { + case BOOL_V: + return " " + tableName + ".str_v = null, " + tableName + ".long_v = null, " + tableName + ".dbl_v = null "; + case STR_V: + return " " + tableName + ".bool_v = null, " + tableName + ".long_v = null, " + tableName + ".dbl_v = null "; + case LONG_V: + return " " + tableName + ".str_v = null, " + tableName + ".bool_v = null, " + tableName + ".dbl_v = null "; + case DBL_V: + return " " + tableName + ".str_v = null, " + tableName + ".long_v = null, " + tableName + ".bool_v = null "; + default: + throw new RuntimeException("Unsupported insert value: [" + notNullValue + "]"); + } + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java new file mode 100644 index 0000000000..a31b0e395b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sqlts; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; + +@Repository +public abstract class AbstractLatestInsertRepository extends AbstractInsertRepository { + + public abstract void saveOrUpdate(TsKvLatestEntity entity); + + protected void processSaveOrUpdate(TsKvLatestEntity entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { + if (entity.getBooleanValue() != null) { + saveOrUpdateBoolean(entity, requestBoolValue); + } + if (entity.getStrValue() != null) { + saveOrUpdateString(entity, requestStrValue); + } + if (entity.getLongValue() != null) { + saveOrUpdateLong(entity, requestLongValue); + } + if (entity.getDoubleValue() != null) { + saveOrUpdateDouble(entity, requestDblValue); + } + } + + @Modifying + protected abstract void saveOrUpdateBoolean(TsKvLatestEntity entity, String query); + + @Modifying + protected abstract void saveOrUpdateString(TsKvLatestEntity entity, String query); + + @Modifying + protected abstract void saveOrUpdateLong(TsKvLatestEntity entity, String query); + + @Modifying + protected abstract void saveOrUpdateDouble(TsKvLatestEntity entity, String query); + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java index 608933c3f7..6f1b9b1ed3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java @@ -19,19 +19,8 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; - @Repository -public abstract class AbstractTimeseriesInsertRepository { - - protected static final String BOOL_V = "bool_v"; - protected static final String STR_V = "str_v"; - protected static final String LONG_V = "long_v"; - protected static final String DBL_V = "dbl_v"; - - @PersistenceContext - protected EntityManager entityManager; +public abstract class AbstractTimeseriesInsertRepository extends AbstractInsertRepository { public abstract void saveOrUpdate(T entity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index 3c87e9f909..d4cbd1c994 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -28,15 +28,10 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; @Transactional public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepository { - private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, dbl_v = null"; - private static final String ON_STR_VALUE_UPDATE_SET_NULLS = "bool_v = null, long_v = null, dbl_v = null"; - private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = "str_v = null, bool_v = null, dbl_v = null"; - private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, bool_v = null"; - - private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , ON_LONG_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); @Override public void saveOrUpdate(TimescaleTsKvEntity entity) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java new file mode 100644 index 0000000000..cea88a5266 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sqlts.ts; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +@SqlTsDao +@HsqlDao +@Repository +@Transactional +public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { + + private static final String TS_KV_LATEST_CONSTRAINT = "(ts_kv_latest.entity_type=A.entity_type AND ts_kv_latest.entity_id=A.entity_id AND ts_kv_latest.key=A.key)"; + + private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, BOOL_V, HSQL_LATEST_ON_BOOL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, STR_V, HSQL_LATEST_ON_STR_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, HSQL_LATEST_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, HSQL_LATEST_ON_DBL_VALUE_UPDATE_SET_NULLS); + + @Override + public void saveOrUpdate(TsKvLatestEntity entity) { + processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); + } + + @Override + protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("bool_v", entity.getBooleanValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateString(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("str_v", entity.getStrValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateLong(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("long_v", entity.getLongValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateDouble(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("dbl_v", entity.getDoubleValue()) + .executeUpdate(); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java index 3ac5cc67a9..927bcd2443 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java @@ -28,19 +28,12 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Transactional public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository { - private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = " ts_kv.str_v = null, ts_kv.long_v = null, ts_kv.dbl_v = null "; - private static final String ON_STR_VALUE_UPDATE_SET_NULLS = " ts_kv.bool_v = null, ts_kv.long_v = null, ts_kv.dbl_v = null "; - private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = " ts_kv.str_v = null, ts_kv.bool_v = null, ts_kv.dbl_v = null "; - private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = " ts_kv.str_v = null, ts_kv.long_v = null, ts_kv.bool_v = null "; + private static final String TS_KV_CONSTRAINT = "(ts_kv.entity_type=A.entity_type AND ts_kv.entity_id=A.entity_id AND ts_kv.key=A.key AND ts_kv.ts=A.ts)"; - private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , ON_LONG_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS); - - private static String getInsertOrUpdateString(String value, String nullValues) { - return "MERGE INTO ts_kv USING(VALUES :entity_type, :entity_id, :key, :ts, :" + value + ") A (entity_type, entity_id, key, ts, " + value + ") ON (ts_kv.entity_type=A.entity_type AND ts_kv.entity_id=A.entity_id AND ts_kv.key=A.key AND ts_kv.ts=A.ts) WHEN MATCHED THEN UPDATE SET ts_kv." + value + " = A." + value + ", ts_kv.ts = A.ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, " + value + ") VALUES (A.entity_type, A.entity_id, A.key, A.ts, A." + value + ")"; - } + private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, HSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, HSQL_ON_STR_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V , HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, HSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); @Override public void saveOrUpdate(TsKvEntity entity) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java index 9e4e281ebd..b70b59604f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java @@ -38,6 +38,7 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; @@ -69,6 +70,9 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese @Autowired private AbstractTimeseriesInsertRepository insertRepository; + @Autowired + private AbstractLatestInsertRepository insertLatestRepository; + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -285,7 +289,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); return insertService.submit(() -> { - tsKvLatestRepository.save(latestEntity); + insertLatestRepository.saveOrUpdate(latestEntity); return null; }); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java new file mode 100644 index 0000000000..c61a74a15d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sqlts.ts; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +@SqlTsDao +@PsqlDao +@Repository +@Transactional +public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { + + private static final String TS_KV_LATEST_CONSTRAINT = "(entity_type, entity_id, key)"; + + private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + + @Override + public void saveOrUpdate(TsKvLatestEntity entity) { + processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); + } + + @Override + protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("bool_v", entity.getBooleanValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateString(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("str_v", entity.getStrValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateLong(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("long_v", entity.getLongValue()) + .executeUpdate(); + } + + @Override + protected void saveOrUpdateDouble(TsKvLatestEntity entity, String query) { + entityManager.createNativeQuery(query) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("key", entity.getKey()) + .setParameter("ts", entity.getTs()) + .setParameter("dbl_v", entity.getDoubleValue()) + .executeUpdate(); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java index 4ed91c28f2..6390a7faee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java @@ -28,19 +28,12 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Transactional public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository { - private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, dbl_v = null"; - private static final String ON_STR_VALUE_UPDATE_SET_NULLS = "bool_v = null, long_v = null, dbl_v = null"; - private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = "str_v = null, bool_v = null, dbl_v = null"; - private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, bool_v = null"; + private static final String TS_KV_CONSTRAINT = "(entity_type, entity_id, key, ts)"; - private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , ON_LONG_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS); - - private static String getInsertOrUpdateString(String value, String nullValues) { - return "INSERT INTO ts_kv (entity_type, entity_id, key, ts, " + value + ") VALUES (:entity_type, :entity_id, :key, :ts, :" + value + ") ON CONFLICT (entity_type, entity_id, key, ts) DO UPDATE SET " + value + " = :" + value + ", ts = :ts," + nullValues; - } + private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); @Override public void saveOrUpdate(TsKvEntity entity) { From 2400ee3ac10ce3e12e27a6c37c18cadb462615f9 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 19 Nov 2019 14:08:18 +0200 Subject: [PATCH 048/120] fix license-headers --- .../thingsboard/server/dao/sqlts/AbstractInsertRepository.java | 2 +- .../server/dao/sqlts/ts/HsqlLatestInsertRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java index bf647ada38..274b07e4fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java index cea88a5266..84250406d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, From 50b89d774c9a935183c3ed9a7759d42cf33bed1d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 18 Nov 2019 15:02:16 +0200 Subject: [PATCH 049/120] refactored DefaultDeviceStateService, added the field useTelemetry(need for saving attributes, if it true, attributes will be save like a telemetry) --- .../state/DefaultDeviceStateService.java | 54 +++++++++++++------ .../src/main/resources/thingsboard.yml | 1 + 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 37d6473d53..99cba1ce9b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -30,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -38,8 +37,10 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -52,7 +53,6 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nullable; @@ -60,6 +60,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -124,6 +125,10 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private long defaultStateCheckIntervalInSec; + @Value("${state.useTelemetry}") + @Getter + private boolean useTelemetry; + // TODO in v2.1 // @Value("${state.defaultStatePersistenceIntervalInSec}") // @Getter @@ -277,8 +282,8 @@ public class DefaultDeviceStateService implements DeviceStateService { if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); - saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts); - saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + save(deviceId, INACTIVITY_ALARM_TIME, ts); + save(deviceId, ACTIVITY_STATE, state.isActive()); } } } @@ -289,7 +294,7 @@ public class DefaultDeviceStateService implements DeviceStateService { long ts = System.currentTimeMillis(); stateData.getState().setLastConnectTime(ts); pushRuleEngineMessage(stateData, CONNECT_EVENT); - saveAttribute(deviceId, LAST_CONNECT_TIME, ts); + save(deviceId, LAST_CONNECT_TIME, ts); } } @@ -299,7 +304,7 @@ public class DefaultDeviceStateService implements DeviceStateService { long ts = System.currentTimeMillis(); stateData.getState().setLastDisconnectTime(ts); pushRuleEngineMessage(stateData, DISCONNECT_EVENT); - saveAttribute(deviceId, LAST_DISCONNECT_TIME, ts); + save(deviceId, LAST_DISCONNECT_TIME, ts); } } @@ -308,11 +313,14 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); long ts = System.currentTimeMillis(); - state.setActive(true); stateData.getState().setLastActivityTime(ts); pushRuleEngineMessage(stateData, ACTIVITY_EVENT); - saveAttribute(deviceId, LAST_ACTIVITY_TIME, ts); - saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + save(deviceId, LAST_ACTIVITY_TIME, ts); + + if (!state.isActive()) { + state.setActive(true); + save(deviceId, ACTIVITY_STATE, state.isActive()); + } } } @@ -345,7 +353,7 @@ public class DefaultDeviceStateService implements DeviceStateService { boolean oldActive = state.isActive(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); if (!oldActive && state.isActive() || oldActive && !state.isActive()) { - saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + save(deviceId, ACTIVITY_STATE, state.isActive()); } } } @@ -464,12 +472,28 @@ public class DefaultDeviceStateService implements DeviceStateService { } } - private void saveAttribute(DeviceId deviceId, String key, long value) { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + private void save(DeviceId deviceId, String key, long value) { + if (useTelemetry) { + tsSubService.saveAndNotify( + TenantId.SYS_TENANT_ID, deviceId, + DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value), System.currentTimeMillis())), + new AttributeSaveCallback(deviceId, key, value)); + } else { + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + } } - private void saveAttribute(DeviceId deviceId, String key, boolean value) { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + private void save(DeviceId deviceId, String key, boolean value) { + if (useTelemetry) { + tsSubService.saveAndNotify( + TenantId.SYS_TENANT_ID, deviceId, + DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value), System.currentTimeMillis())), + new AttributeSaveCallback(deviceId, key, value)); + } else { + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + } } private class AttributeSaveCallback implements FutureCallback { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2d55e47ea0..93a685221e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -403,6 +403,7 @@ audit-log: state: defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:10}" defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}" + useTelemetry: "${USE_TELEMETRY:true}" kafka: enabled: true From bc08e6204f6f5918c547ee78848d72d1e38edfbc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 18 Nov 2019 17:46:37 +0200 Subject: [PATCH 050/120] refactored method save from DefaultDeviceStateService --- .../server/service/state/DefaultDeviceStateService.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 99cba1ce9b..eb280f91a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.page.TextPageLink; @@ -476,8 +476,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (useTelemetry) { tsSubService.saveAndNotify( TenantId.SYS_TENANT_ID, deviceId, - DataConstants.SERVER_SCOPE, - Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value), System.currentTimeMillis())), + Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), new AttributeSaveCallback(deviceId, key, value)); } else { tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); @@ -488,8 +487,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (useTelemetry) { tsSubService.saveAndNotify( TenantId.SYS_TENANT_ID, deviceId, - DataConstants.SERVER_SCOPE, - Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value), System.currentTimeMillis())), + Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), new AttributeSaveCallback(deviceId, key, value)); } else { tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); From 5ee58710a561db46fe1993ea8e4e8f7e45b29a88 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 19 Nov 2019 09:43:10 +0200 Subject: [PATCH 051/120] refactored renamed useTelemetry to persistToTelemetry --- .../server/service/state/DefaultDeviceStateService.java | 8 ++++---- application/src/main/resources/thingsboard.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index eb280f91a6..f7904bf1bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -125,9 +125,9 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private long defaultStateCheckIntervalInSec; - @Value("${state.useTelemetry}") + @Value("${state.persistToTelemetry}") @Getter - private boolean useTelemetry; + private boolean persistToTelemetry; // TODO in v2.1 // @Value("${state.defaultStatePersistenceIntervalInSec}") @@ -473,7 +473,7 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void save(DeviceId deviceId, String key, long value) { - if (useTelemetry) { + if (persistToTelemetry) { tsSubService.saveAndNotify( TenantId.SYS_TENANT_ID, deviceId, Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), @@ -484,7 +484,7 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void save(DeviceId deviceId, String key, boolean value) { - if (useTelemetry) { + if (persistToTelemetry) { tsSubService.saveAndNotify( TenantId.SYS_TENANT_ID, deviceId, Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 93a685221e..4c70768c87 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -403,7 +403,7 @@ audit-log: state: defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:10}" defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}" - useTelemetry: "${USE_TELEMETRY:true}" + persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" kafka: enabled: true From 9c5dd4345c3d7a226feb9f9549c71e296cd2fe4f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 19 Nov 2019 12:37:55 +0200 Subject: [PATCH 052/120] added default value to persistToTelemetry --- .../server/service/state/DefaultDeviceStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index f7904bf1bc..aee2444b2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -125,7 +125,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private long defaultStateCheckIntervalInSec; - @Value("${state.persistToTelemetry}") + @Value("${state.persistToTelemetry:false}") @Getter private boolean persistToTelemetry; From e253b6443c5aa3d08f4b8e3b4dccbbd062766cf2 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 14 Nov 2019 18:52:49 +0200 Subject: [PATCH 053/120] Update widget for backward compatibility --- .../system/widget_bundles/input_widgets.json | 2 +- .../app/widget/lib/multiple-input-widget.js | 280 +++++++++++------- .../widget/lib/multiple-input-widget.tpl.html | 30 +- 3 files changed, 192 insertions(+), 120 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 7328dc7fb3..9f2ac9c178 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -319,7 +319,7 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-resize', self.ctx.$scope.formId);\r\n}\r\n", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n\r\nself.onResize = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-resize', self.ctx.$scope.formId);\r\n}\r\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"showResultMessage\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n \"required\",\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index 1993a01aa4..64bb6b7a81 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -47,14 +47,14 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, vm.entityDetected = false; vm.isAllParametersValid = true; - vm.data = []; + vm.sources = []; vm.datasources = null; vm.discardAll = discardAll; vm.inputChanged = inputChanged; vm.save = save; - $scope.$watch('vm.ctx', function() { + $scope.$watch('vm.ctx', function () { if (vm.ctx && vm.ctx.defaultSubscription) { vm.settings = vm.ctx.settings; vm.widgetConfig = vm.ctx.widgetConfig; @@ -65,100 +65,113 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, } }); - $scope.$on('multiple-input-data-updated', function(event, formId) { + $scope.$on('multiple-input-data-updated', function (event, formId) { if (vm.formId == formId) { updateWidgetData(vm.subscription.data); $scope.$digest(); } }); - $scope.$on('multiple-input-resize', function(event, formId) { + $scope.$on('multiple-input-resize', function (event, formId) { if (vm.formId == formId) { updateWidgetDisplaying(); } }); function discardAll() { - for (var i = 0; i < vm.data.length; i++) { - vm.data[i].data.currentValue = vm.data[i].data.originalValue; + for (var i = 0; i < vm.sources.length; i++) { + for (var j = 0; j < vm.sources[i].keys.length; j++) { + vm.sources[i].keys[j].data.currentValue = vm.sources[i].keys[j].data.originalValue; + } } $scope.multipleInputForm.$setPristine(); } - function inputChanged(key) { + function inputChanged(source, key) { if (!vm.settings.showActionButtons) { if (!key.settings.required || (key.settings.required && key.data && angular.isDefined(key.data.currentValue))) { - vm.save(key); + var dataToSave = { + datasource: source.datasource, + keys: [key] + }; + vm.save(dataToSave); } } } - function save(key) { + function save(dataToSave) { var tasks = []; - var serverAttributes = [], sharedAttributes = [], telemetry = []; var config = { ignoreLoading: !vm.settings.showActionButtons }; var data; - if (key) { - data = [key]; + if (dataToSave) { + data = [dataToSave]; } else { - data = vm.data; + data = vm.sources; } for (let i = 0; i < data.length; i++) { - var item = data[i]; - if (item.data.currentValue !== item.data.originalValue) { - var attribute = { - key: item.name - }; - switch (item.settings.dataKeyValueType) { - case 'dateTime': - case 'date': - attribute.value = item.data.currentValue.getTime(); - break; - case 'time': - attribute.value = item.data.currentValue.getTime() - moment().startOf('day').valueOf();//eslint-disable-line - break; - default: - attribute.value = item.data.currentValue; - } + var serverAttributes = [], sharedAttributes = [], telemetry = []; + for (let j = 0; j < data[i].keys.length; j++) { + var key = data[i].keys[j]; + if (key.data.currentValue !== key.data.originalValue) { + var attribute = { + key: key.name + }; + if (key.data.currentValue) { + switch (key.settings.dataKeyValueType) { + case 'dateTime': + case 'date': + attribute.value = key.data.currentValue.getTime(); + break; + case 'time': + attribute.value = key.data.currentValue.getTime() - moment().startOf('day').valueOf();//eslint-disable-line + break; + default: + attribute.value = key.data.currentValue; + } + } else { + attribute.value = key.data.currentValue; + } - switch (item.settings.dataKeyType) { - case 'shared': - sharedAttributes.push(attribute); - break; - case 'timeseries': - telemetry.push(attribute); - break; - default: - serverAttributes.push(attribute); + switch (key.settings.dataKeyType) { + case 'shared': + sharedAttributes.push(attribute); + break; + case 'timeseries': + telemetry.push(attribute); + break; + default: + serverAttributes.push(attribute); + } } } + if (serverAttributes.length) { + tasks.push(attributeService.saveEntityAttributes( + data[i].datasource.entityType, + data[i].datasource.entityId, + types.attributesScope.server.value, + serverAttributes, + config)); + } + if (sharedAttributes.length) { + tasks.push(attributeService.saveEntityAttributes( + data[i].datasource.entityType, + data[i].datasource.entityId, + types.attributesScope.shared.value, + sharedAttributes, + config)); + } + if (telemetry.length) { + tasks.push(attributeService.saveEntityTimeseries( + data[i].datasource.entityType, + data[i].datasource.entityId, + types.latestTelemetry.value, + telemetry, + config)); + } } - for (let i = 0; i < serverAttributes.length; i++) { - tasks.push(attributeService.saveEntityAttributes( - vm.datasources[0].entityType, - vm.datasources[0].entityId, - types.attributesScope.server.value, - serverAttributes, - config)); - } - for (let i = 0; i < sharedAttributes.length; i++) { - tasks.push(attributeService.saveEntityAttributes( - vm.datasources[0].entityType, - vm.datasources[0].entityId, - types.attributesScope.shared.value, - sharedAttributes, - config)); - } - for (let i = 0; i < telemetry.length; i++) { - tasks.push(attributeService.saveEntityTimeseries( - vm.datasources[0].entityType, - vm.datasources[0].entityId, - types.latestTelemetry.value, - telemetry, - config)); - } + if (tasks.length) { $q.all(tasks).then( function success() { @@ -173,6 +186,8 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, } } ); + } else { + $scope.multipleInputForm.$setPristine(); } } @@ -186,6 +201,18 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, vm.ctx.widgetTitle = vm.widgetTitle; + //For backward compatibility + if (angular.isUndefined(vm.settings.showActionButtons)) { + vm.settings.showActionButtons = true; + } + if (angular.isUndefined(vm.settings.fieldsAlignment)) { + vm.settings.fieldsAlignment = 'row'; + } + if (angular.isUndefined(vm.settings.fieldsInRow)) { + vm.settings.fieldsInRow = 2; + } + //For backward compatibility + vm.isVerticalAlignment = !(vm.settings.fieldsAlignment === 'row'); if (!vm.isVerticalAlignment && vm.settings.fieldsInRow) { @@ -195,60 +222,105 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, function updateDatasources() { if (vm.datasources && vm.datasources.length) { - var datasource = vm.datasources[0]; - if (datasource.type === types.datasourceType.entity) { - for (var i = 0; i < datasource.dataKeys.length; i++) { - if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[i].settings.dataKeyType == 'shared')) { - vm.isAllParametersValid = false; + vm.entityDetected = true; + for (var i = 0; i < vm.datasources.length; i++) { + var datasource = vm.datasources[i]; + var source = { + datasource: datasource, + keys: [] + }; + if (datasource.type === types.datasourceType.entity) { + for (var j = 0; j < datasource.dataKeys.length; j++) { + if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[j].settings.dataKeyType == 'shared')) { + vm.isAllParametersValid = false; + } + source.keys.push(datasource.dataKeys[j]); + if (source.keys[j].units) { + source.keys[j].label += ' (' + source.keys[j].units + ')'; + } + source.keys[j].data = {}; + + //For backward compatibility + if (angular.isUndefined(source.keys[j].settings.dataKeyType)) { + if (vm.settings.attributesShared === true) { + source.keys[j].settings.dataKeyType = 'shared'; + } else { + source.keys[j].settings.dataKeyType = 'server'; + } + } + + if (angular.isUndefined(source.keys[j].settings.dataKeyValueType)) { + if (source.keys[j].settings.inputTypeNumber === true) { + source.keys[j].settings.dataKeyValueType = 'double'; + } else { + source.keys[j].settings.dataKeyValueType = 'string'; + } + } + + if (angular.isUndefined(source.keys[j].settings.isEditable)) { + if (source.keys[j].settings.readOnly === true) { + source.keys[j].settings.isEditable = 'readonly'; + } else { + source.keys[j].settings.isEditable = 'editable'; + } + } + //For backward compatibility + } - vm.data.push(datasource.dataKeys[i]); - vm.data[i].data = {}; + } else { + vm.entityDetected = false; } - vm.entityDetected = true; + vm.sources.push(source); } } } function updateWidgetData(data) { - for (var i = 0; i < vm.data.length; i++) { - var keyData = data[i].data; - if (keyData && keyData.length) { - var value; - switch (vm.data[i].settings.dataKeyValueType) { - case 'dateTime': - case 'date': - value = moment(keyData[0][1]).toDate(); // eslint-disable-line - break; - case 'time': - value = moment().startOf('day').add(keyData[0][1], 'ms').toDate(); // eslint-disable-line - break; - case 'booleanCheckbox': - case 'booleanSwitch': - value = (keyData[0][1] === 'true'); - break; - default: - value = keyData[0][1]; - } + var dataIndex = 0; + for (var i = 0; i < vm.sources.length; i++) { + var source = vm.sources[i]; + for (var j = 0; j < source.keys.length; j++) { + var keyData = data[dataIndex].data; + var key = source.keys[j]; + if (keyData && keyData.length) { + var value; + switch (key.settings.dataKeyValueType) { + case 'dateTime': + case 'date': + value = moment(keyData[0][1]).toDate(); // eslint-disable-line + break; + case 'time': + value = moment().startOf('day').add(keyData[0][1], 'ms').toDate(); // eslint-disable-line + break; + case 'booleanCheckbox': + case 'booleanSwitch': + value = (keyData[0][1] === 'true'); + break; + default: + value = keyData[0][1]; + } - vm.data[i].data = { - currentValue: value, - originalValue: value - }; - } + key.data = { + currentValue: value, + originalValue: value + }; + } - if (vm.data[i].settings.isEditable === 'editable' && vm.data[i].settings.disabledOnDataKey) { - var conditions = data.filter((item) => { - return item.dataKey.name === vm.data[i].settings.disabledOnDataKey; - }); - if (conditions && conditions.length) { - if (conditions[0].data.length) { - if (conditions[0].data[0][1] === 'false') { - vm.data[i].settings.disabledOnCondition = true; - } else { - vm.data[i].settings.disabledOnCondition = !conditions[0].data[0][1]; + if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) { + var conditions = data.filter((item) => { + return item.dataKey.name === key.settings.disabledOnDataKey; + }); + if (conditions && conditions.length) { + if (conditions[0].data.length) { + if (conditions[0].data[0][1] === 'false') { + key.settings.disabledOnCondition = true; + } else { + key.settings.disabledOnCondition = !conditions[0].data[0][1]; + } } } } + dataIndex++; } } } diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index e375af49c0..3240d97c1b 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -17,9 +17,9 @@ -->
-
-
-
+
+
+
@@ -32,13 +32,13 @@ ng-required="key.settings.required" type="text" md-select-on-focus - ng-blur="vm.inputChanged(key)"> + ng-blur="vm.inputChanged(source,key)">
{{ key.settings.requiredErrorMessage }}
-
+
@@ -52,13 +52,13 @@ type="number" step="key.settings.step" md-select-on-focus - ng-blur="vm.inputChanged(key)"> + ng-blur="vm.inputChanged(source,key)">
{{ key.settings.requiredErrorMessage }}
-
+
@@ -73,26 +73,26 @@ step="key.settings.step" md-select-on-focus ng-pattern="/^-?[0-9]+$/" - ng-blur="vm.inputChanged(key)"> + ng-blur="vm.inputChanged(source,key)">
{{ key.settings.requiredErrorMessage }}
value.invalid-integer-value
-
+
+ ng-change="vm.inputChanged(source,key)"> {{key.label}}
-
+
{{key.label}} @@ -100,7 +100,7 @@
@@ -108,7 +108,7 @@ ng-if="key.settings.dataKeyValueType !== 'time'" mdp-disabled="key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition" ng-model="key.data.currentValue" - ng-change="vm.inputChanged(key)" + ng-change="vm.inputChanged(source,key)" mdp-placeholder="{{ 'widgets.input-widgets.date' | translate }}">
{{ key.settings.requiredErrorMessage }}
@@ -118,7 +118,7 @@ ng-if="key.settings.dataKeyValueType !== 'date'" mdp-disabled="key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition" ng-model="key.data.currentValue" - ng-change="vm.inputChanged(key)" + ng-change="vm.inputChanged(source,key)" mdp-placeholder="{{ 'widgets.input-widgets.time' | translate }}" mdp-auto-switch="true">
From 9e9b0d7595c6731c1ebd785cafd73481c038c5c1 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 14 Nov 2019 20:42:11 +0200 Subject: [PATCH 054/120] Update widget settings --- .../system/widget_bundles/input_widgets.json | 2 +- .../app/widget/lib/multiple-input-widget.js | 15 +- .../app/widget/lib/multiple-input-widget.scss | 10 + .../widget/lib/multiple-input-widget.tpl.html | 203 +++++++++--------- 4 files changed, 127 insertions(+), 103 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 9f2ac9c178..2cc297b263 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -320,7 +320,7 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n\r\nself.onResize = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-resize', self.ctx.$scope.formId);\r\n}\r\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"showResultMessage\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified (only if action buttons are visible)\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"updateAllValues\",\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n \"required\",\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index 64bb6b7a81..b559f9b7ee 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -53,6 +53,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, vm.discardAll = discardAll; vm.inputChanged = inputChanged; vm.save = save; + vm.getGroupTitle = getGroupTitle; $scope.$watch('vm.ctx', function () { if (vm.ctx && vm.ctx.defaultSubscription) { @@ -114,7 +115,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, var serverAttributes = [], sharedAttributes = [], telemetry = []; for (let j = 0; j < data[i].keys.length; j++) { var key = data[i].keys[j]; - if (key.data.currentValue !== key.data.originalValue) { + if ((key.data.currentValue !== key.data.originalValue) || vm.settings.updateAllValues) { var attribute = { key: key.name }; @@ -131,7 +132,11 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, attribute.value = key.data.currentValue; } } else { - attribute.value = key.data.currentValue; + if (key.data.currentValue === '') { + attribute.value = null; + } else { + attribute.value = key.data.currentValue; + } } switch (key.settings.dataKeyType) { @@ -201,6 +206,8 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, vm.ctx.widgetTitle = vm.widgetTitle; + vm.settings.groupTitle = vm.settings.groupTitle || "${entityName}"; + //For backward compatibility if (angular.isUndefined(vm.settings.showActionButtons)) { vm.settings.showActionButtons = true; @@ -329,4 +336,8 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, vm.changeAlignment = (vm.ctx.$container[0].offsetWidth < 620); vm.smallWidthContainer = (vm.ctx.$container[0].offsetWidth < 420); } + + function getGroupTitle(datasource) { + return utils.createLabelFromDatasource(datasource, vm.settings.groupTitle); + } } diff --git a/ui/src/app/widget/lib/multiple-input-widget.scss b/ui/src/app/widget/lib/multiple-input-widget.scss index fdeafae93b..8b6d7a0fdb 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.scss +++ b/ui/src/app/widget/lib/multiple-input-widget.scss @@ -18,6 +18,16 @@ overflow-x: hidden; overflow-y: auto; + .fields-group { + padding: 0 8px; + margin: 10px 0; + border: 1px groove rgba(0, 0, 0, .25); + + legend { + color: rgba(0, 0, 0, .7); + } + } + .input-field { padding-right: 10px; diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index 3240d97c1b..247047fe90 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -17,118 +17,121 @@ -->
-
-
-
- - - - {{key.settings.icon}} - - -
-
{{ key.settings.requiredErrorMessage }}
-
-
-
-
- - - - {{key.settings.icon}} - - -
-
{{ key.settings.requiredErrorMessage }}
-
-
-
-
- - - - {{key.settings.icon}} - - -
-
{{ key.settings.requiredErrorMessage }}
-
value.invalid-integer-value
-
-
-
-
+
+ {{ vm.getGroupTitle(source.datasource) }} +
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
value.invalid-integer-value
+
+
+
+
{{key.label}} -
-
+
+
+ ng-disabled="key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition" + ng-model="key.data.currentValue" + ng-change="vm.inputChanged(source,key)" + aria-label="{{key.label}}" + md-invert> {{key.label}} -
-
- -
- -
-
{{ key.settings.requiredErrorMessage }}
-
-
- -
-
{{ key.settings.requiredErrorMessage }}
-
-
+
+
+ +
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
-
+
{{ 'action.undo' | translate }} From 0e3a421fbbf7757d720070538b582d0b7ab4e063 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 15 Nov 2019 11:09:31 +0200 Subject: [PATCH 055/120] Format & clear unnecessary --- .../app/widget/lib/multiple-input-widget.js | 6 ++-- .../widget/lib/multiple-input-widget.tpl.html | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index b559f9b7ee..11b74fd697 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -249,7 +249,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, //For backward compatibility if (angular.isUndefined(source.keys[j].settings.dataKeyType)) { - if (vm.settings.attributesShared === true) { + if (vm.settings.attributesShared) { source.keys[j].settings.dataKeyType = 'shared'; } else { source.keys[j].settings.dataKeyType = 'server'; @@ -257,7 +257,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, } if (angular.isUndefined(source.keys[j].settings.dataKeyValueType)) { - if (source.keys[j].settings.inputTypeNumber === true) { + if (source.keys[j].settings.inputTypeNumber) { source.keys[j].settings.dataKeyValueType = 'double'; } else { source.keys[j].settings.dataKeyValueType = 'string'; @@ -265,7 +265,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, } if (angular.isUndefined(source.keys[j].settings.isEditable)) { - if (source.keys[j].settings.readOnly === true) { + if (source.keys[j].settings.readOnly) { source.keys[j].settings.isEditable = 'readonly'; } else { source.keys[j].settings.isEditable = 'editable'; diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index 247047fe90..bbe55ffc13 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -18,9 +18,12 @@
- {{ vm.getGroupTitle(source.datasource) }} -
-
+ {{ vm.getGroupTitle(source.datasource) }} + +
+
@@ -82,7 +85,7 @@
-
+
-
+
-
- +
+ {{ 'action.undo' | translate }} - + {{ 'action.save' | translate }}
-
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
+
{{ 'widgets.input-widgets.not-allowed-entity' | translate }}
From 18767381f90620c42b6bf38ee501cedba6883084 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Thu, 14 Nov 2019 14:17:47 +0200 Subject: [PATCH 056/120] originator telemetry node added limit param --- .../engine/metadata/TbGetTelemetryNode.java | 20 ++++++++++++------- .../TbGetTelemetryNodeConfiguration.java | 4 +++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 0de8a93173..6bcc3e3d52 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -26,12 +26,12 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -81,7 +81,7 @@ public class TbGetTelemetryNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class); tsKeyNames = config.getLatestTsKeyNames(); - limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1; + limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? validateLimit(config.getLimit()) : 1; fetchMode = config.getFetchMode(); orderByFetchAll = config.getOrderBy(); if (StringUtils.isEmpty(orderByFetchAll)) { @@ -136,7 +136,7 @@ public class TbGetTelemetryNode implements TbNode { private void process(List entries, TbMsg msg) { ObjectNode resultNode = mapper.createObjectNode(); - if (limit == MAX_FETCH_SIZE) { + if (FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { entries.forEach(entry -> processSingle(resultNode, entry)); @@ -156,12 +156,10 @@ public class TbGetTelemetryNode implements TbNode { private void processArray(ObjectNode node, TsKvEntry entry) { if (node.has(entry.getKey())) { ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey()); - ObjectNode obj = buildNode(entry); - arrayNode.add(obj); + arrayNode.add(buildNode(entry)); } else { ArrayNode arrayNode = mapper.createArrayNode(); - ObjectNode obj = buildNode(entry); - arrayNode.add(obj); + arrayNode.add(buildNode(entry)); node.set(entry.getKey(), arrayNode); } } @@ -254,6 +252,14 @@ public class TbGetTelemetryNode implements TbNode { return pattern.replaceAll("[${}]", ""); } + private int validateLimit(int limit) { + if (limit != 0) { + return limit; + } else { + return MAX_FETCH_SIZE; + } + } + @Data @NoArgsConstructor private static class Interval { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java index 05335879ca..7a312c34fc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java @@ -45,7 +45,8 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration latestTsKeyNames; @@ -62,6 +63,7 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration Date: Thu, 14 Nov 2019 14:23:45 +0200 Subject: [PATCH 057/120] originator telemetry node added limit param, fixed ui --- .../public/static/rulenode/rulenode-core-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index a9f344d85e..ac3a5c588a 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,6 +1,6 @@ -!function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; -},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'; +!function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; +},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
tb.rulenode.limit-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'; },function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),$=n(60),k=i($),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),J=n(65),W=i(J);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",W.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}} -a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),$=n(103),k=i($);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.", -"delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),$=n(103),k=i($);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity", +"check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file From 1d29a8d9a686bce1332dbfe7bf865bdb157a523e Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 22 Nov 2019 15:11:32 +0200 Subject: [PATCH 058/120] Batch attribute updates * added batch support to AttributeKvInsertRepository * added logs for testing * added batch support to AttributeKvInsertRepository * added logs for testing * Code review part 1 * Improvements * Batch Update Implementation * added realization SaveOrUpdate batch to Hsql and refactored * refactored --- .../src/main/resources/thingsboard.yml | 5 + .../sql/ScheduledLogExecutorComponent.java | 46 +++++++ .../server/dao/sql/TbSqlBlockingQueue.java | 114 +++++++++++++++ .../dao/sql/TbSqlBlockingQueueParams.java | 31 +++++ .../server/dao/sql/TbSqlQueue.java | 30 ++++ .../server/dao/sql/TbSqlQueueElement.java | 33 +++++ .../AttributeKvInsertRepository.java | 130 ++++++++++++++++++ .../HsqlAttributesInsertRepository.java | 48 +++++++ .../dao/sql/attributes/JpaAttributeDao.java | 47 ++++++- 9 files changed, 480 insertions(+), 4 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueueParams.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueue.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueueElement.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 4c70768c87..5015ff090d 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -194,6 +194,11 @@ sql: ts_inserts_executor_type: "${SQL_TS_INSERTS_EXECUTOR_TYPE:fixed}" # Specify thread pool size for FIXED executor service type ts_inserts_fixed_thread_pool_size: "${SQL_TS_INSERTS_FIXED_THREAD_POOL_SIZE:200}" + # Specify batch size for persisting attribute updates + attributes: + batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:1000}" # Actor system parameters actors: diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java new file mode 100644 index 0000000000..da20ff8c21 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Component +public class ScheduledLogExecutorComponent { + + private ScheduledExecutorService schedulerLogExecutor; + + @PostConstruct + public void init() { + schedulerLogExecutor = Executors.newSingleThreadScheduledExecutor(); + } + + @PreDestroy + public void stop() { + if (schedulerLogExecutor != null) { + schedulerLogExecutor.shutdownNow(); + } + } + + public void scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + schedulerLogExecutor.scheduleAtFixedRate(command, initialDelay, period, unit); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java new file mode 100644 index 0000000000..7630ccdaad --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java @@ -0,0 +1,114 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Slf4j +public class TbSqlBlockingQueue implements TbSqlQueue { + + private final BlockingQueue> queue = new LinkedBlockingQueue<>(); + private final AtomicInteger addedCount = new AtomicInteger(); + private final AtomicInteger savedCount = new AtomicInteger(); + private final AtomicInteger failedCount = new AtomicInteger(); + private final TbSqlBlockingQueueParams params; + + private ExecutorService executor; + private ScheduledLogExecutorComponent logExecutor; + + public TbSqlBlockingQueue(TbSqlBlockingQueueParams params) { + this.params = params; + } + + @Override + public void init(ScheduledLogExecutorComponent logExecutor, Consumer> saveFunction) { + this.logExecutor = logExecutor; + executor = Executors.newSingleThreadExecutor(); + executor.submit(() -> { + String logName = params.getLogName(); + int batchSize = params.getBatchSize(); + long maxDelay = params.getMaxDelay(); + List> entities = new ArrayList<>(batchSize); + while (!Thread.interrupted()) { + try { + long currentTs = System.currentTimeMillis(); + TbSqlQueueElement attr = queue.poll(maxDelay, TimeUnit.MILLISECONDS); + if (attr == null) { + continue; + } else { + entities.add(attr); + } + queue.drainTo(entities, batchSize - 1); + boolean fullPack = entities.size() == batchSize; + log.debug("[{}] Going to save {} entities", logName, entities.size()); + saveFunction.accept(entities.stream().map(TbSqlQueueElement::getEntity).collect(Collectors.toList())); + entities.forEach(v -> v.getFuture().set(null)); + savedCount.addAndGet(entities.size()); + if (!fullPack) { + long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs); + if (remainingDelay > 0) { + Thread.sleep(remainingDelay); + } + } + } catch (Exception e) { + failedCount.addAndGet(entities.size()); + entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(e)); + if (e instanceof InterruptedException) { + log.info("[{}] Queue polling was interrupted", logName); + break; + } else { + log.error("[{}] Failed to save {} entities", logName, entities.size(), e); + } + } finally { + entities.clear(); + } + } + }); + + logExecutor.scheduleAtFixedRate(() -> { + log.info("Attributes queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", + queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); + }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); + } + + @Override + public void destroy() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Override + public ListenableFuture add(E element) { + SettableFuture future = SettableFuture.create(); + queue.add(new TbSqlQueueElement<>(future, element)); + addedCount.incrementAndGet(); + return future; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueueParams.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueueParams.java new file mode 100644 index 0000000000..9afe19a61a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueueParams.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +@Builder +public class TbSqlBlockingQueueParams { + + private final String logName; + private final int batchSize; + private final long maxDelay; + private final long statsPrintIntervalMs; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueue.java new file mode 100644 index 0000000000..27c6bc9509 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueue.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.List; +import java.util.function.Consumer; + +public interface TbSqlQueue { + + void init(ScheduledLogExecutorComponent logExecutor, Consumer> saveFunction); + + void destroy(); + + ListenableFuture add(E element); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueueElement.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueueElement.java new file mode 100644 index 0000000000..7c95d768e7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlQueueElement.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql; + +import com.google.common.util.concurrent.SettableFuture; +import lombok.Getter; + +public final class TbSqlQueueElement { + @Getter + private final SettableFuture future; + @Getter + private final E entity; + + public TbSqlQueueElement(SettableFuture future, E entity) { + this.future = future; + this.entity = entity; + } +} + + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java index e50a60aed9..89843048a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java @@ -15,23 +15,51 @@ */ package org.thingsboard.server.dao.sql.attributes; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.util.SqlDao; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; @SqlDao @Repository +@Slf4j public abstract class AttributeKvInsertRepository { + private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, last_update_ts = ? " + + "WHERE entity_type = ? and entity_id = ? and attribute_type =? and attribute_key = ?;"; + + private static final String INSERT_OR_UPDATE = + "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) " + + "DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, last_update_ts = ?;"; + protected static final String BOOL_V = "bool_v"; protected static final String STR_V = "str_v"; protected static final String LONG_V = "long_v"; protected static final String DBL_V = "dbl_v"; + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + private TransactionTemplate transactionTemplate; + @PersistenceContext protected EntityManager entityManager; @@ -99,4 +127,106 @@ public abstract class AttributeKvInsertRepository { .setParameter("last_update_ts", entity.getLastUpdateTs()) .executeUpdate(); } + + protected void saveOrUpdate(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getStrValue()); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(2, entities.get(i).getLongValue()); + } else { + ps.setNull(2, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(3, entities.get(i).getDoubleValue()); + } else { + ps.setNull(3, Types.DOUBLE); + } + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(4, entities.get(i).getBooleanValue()); + } else { + ps.setNull(4, Types.BOOLEAN); + } + + ps.setLong(5, entities.get(i).getLastUpdateTs()); + ps.setString(6, entities.get(i).getId().getEntityType().name()); + ps.setString(7, entities.get(i).getId().getEntityId()); + ps.setString(8, entities.get(i).getId().getAttributeType()); + ps.setString(9, entities.get(i).getId().getAttributeKey()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + + int updatedCount = 0; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + updatedCount++; + } + } + + List insertEntities = new ArrayList<>(updatedCount); + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + insertEntities.add(entities.get(i)); + } + } + + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, insertEntities.get(i).getId().getEntityType().name()); + ps.setString(2, insertEntities.get(i).getId().getEntityId()); + ps.setString(3, insertEntities.get(i).getId().getAttributeType()); + ps.setString(4, insertEntities.get(i).getId().getAttributeKey()); + ps.setString(5, insertEntities.get(i).getStrValue()); + ps.setString(10, insertEntities.get(i).getStrValue()); + + if (insertEntities.get(i).getLongValue() != null) { + ps.setLong(6, insertEntities.get(i).getLongValue()); + ps.setLong(11, insertEntities.get(i).getLongValue()); + } else { + ps.setNull(6, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (insertEntities.get(i).getDoubleValue() != null) { + ps.setDouble(7, insertEntities.get(i).getDoubleValue()); + ps.setDouble(12, insertEntities.get(i).getDoubleValue()); + } else { + ps.setNull(7, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + } + + if (insertEntities.get(i).getBooleanValue() != null) { + ps.setBoolean(8, insertEntities.get(i).getBooleanValue()); + ps.setBoolean(13, insertEntities.get(i).getBooleanValue()); + } else { + ps.setNull(8, Types.BOOLEAN); + ps.setNull(13, Types.BOOLEAN); + } + + ps.setLong(9, insertEntities.get(i).getLastUpdateTs()); + ps.setLong(14, insertEntities.get(i).getLastUpdateTs()); + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }); + } + }); + } + } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java index f2343cd7fe..425832f344 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java @@ -21,6 +21,9 @@ import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlDao; +import java.sql.Types; +import java.util.List; + @SqlDao @HsqlDao @Repository @@ -37,6 +40,17 @@ public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository private static final String INSERT_LONG_STATEMENT = getInsertOrUpdateString(LONG_V, ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + + "ON (attribute_kv.entity_type=A.entity_type " + + "AND attribute_kv.entity_id=A.entity_id " + + "AND attribute_kv.attribute_type=A.attribute_type " + + "AND attribute_kv.attribute_key=A.attribute_key) " + + "WHEN MATCHED THEN UPDATE SET attribute_kv.str_v = A.str_v, attribute_kv.long_v = A.long_v, attribute_kv.dbl_v = A.dbl_v, attribute_kv.bool_v = A.bool_v, attribute_kv.last_update_ts = A.last_update_ts " + + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + + "VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A.str_v, A.long_v, A.dbl_v, A.bool_v, A.last_update_ts)"; + @Override public void saveOrUpdate(AttributeKvEntity entity) { processSaveOrUpdate(entity, INSERT_BOOL_STATEMENT, INSERT_STR_STATEMENT, INSERT_LONG_STATEMENT, INSERT_DBL_STATEMENT); @@ -45,4 +59,38 @@ public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository private static String getInsertOrUpdateString(String value, String nullValues) { return "MERGE INTO attribute_kv USING(VALUES :entity_type, :entity_id, :attribute_type, :attribute_key, :" + value + ", :last_update_ts) A (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) ON (attribute_kv.entity_type=A.entity_type AND attribute_kv.entity_id=A.entity_id AND attribute_kv.attribute_type=A.attribute_type AND attribute_kv.attribute_key=A.attribute_key) WHEN MATCHED THEN UPDATE SET attribute_kv." + value + " = A." + value + ", attribute_kv.last_update_ts = A.last_update_ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A." + value + ", A.last_update_ts)"; } + + + @Override + protected void saveOrUpdate(List entities) { + entities.forEach(entity -> { + jdbcTemplate.update(INSERT_OR_UPDATE, ps -> { + ps.setString(1, entity.getId().getEntityType().name()); + ps.setString(2, entity.getId().getEntityId()); + ps.setString(3, entity.getId().getAttributeType()); + ps.setString(4, entity.getId().getAttributeKey()); + ps.setString(5, entity.getStrValue()); + + if (entity.getLongValue() != null) { + ps.setLong(6, entity.getLongValue()); + } else { + ps.setNull(6, Types.BIGINT); + } + + if (entity.getDoubleValue() != null) { + ps.setDouble(7, entity.getDoubleValue()); + } else { + ps.setNull(7, Types.DOUBLE); + } + + if (entity.getBooleanValue() != null) { + ps.setBoolean(8, entity.getBooleanValue()); + } else { + ps.setNull(8, Types.BOOLEAN); + } + + ps.setLong(9, entity.getLastUpdateTs()); + }); + }); + } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index b8ad6f8271..2b8c7e5ab2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; @@ -30,8 +31,13 @@ import org.thingsboard.server.dao.attributes.AttributesDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.util.SqlDao; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -44,12 +50,45 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; @SqlDao public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService implements AttributesDao { + @Autowired + ScheduledLogExecutorComponent logExecutor; + @Autowired private AttributeKvRepository attributeKvRepository; @Autowired private AttributeKvInsertRepository attributeKvInsertRepository; + @Value("${sql.attributes.batch_size:1000}") + private int batchSize; + + @Value("${sql.attributes.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.attributes.stats_print_interval_ms:1000}") + private long statsPrintIntervalMs; + + private TbSqlBlockingQueue queue; + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + .logName("Attributes") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .build(); + queue = new TbSqlBlockingQueue<>(params); + queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v)); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); + } + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, String attributeType, String attributeKey) { AttributeKvCompositeKey compositeKey = @@ -89,12 +128,12 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); entity.setLongValue(attribute.getLongValue().orElse(null)); entity.setBooleanValue(attribute.getBooleanValue().orElse(null)); - return service.submit(() -> { - attributeKvInsertRepository.saveOrUpdate(entity); - return null; - }); + return addToQueue(entity); } + private ListenableFuture addToQueue(AttributeKvEntity entity) { + return queue.add(entity); + } @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys) { From 78e6b6cdcedeb8de728e2d9aafb1d5159b120b96 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Wed, 20 Nov 2019 19:37:31 +0200 Subject: [PATCH 059/120] added ability to fetch latest values with ts --- .../metadata/TbAbstractGetAttributesNode.java | 54 +++++++++++++++---- .../TbGetAttributesNodeConfiguration.java | 2 + .../TbGetDeviceAttrNodeConfiguration.java | 1 + 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index df6bcf9f58..7a26faa8f4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -15,6 +15,10 @@ */ package org.thingsboard.rule.engine.metadata; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.collections.CollectionUtils; @@ -40,11 +44,18 @@ import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; public abstract class TbAbstractGetAttributesNode implements TbNode { + private static ObjectMapper mapper = new ObjectMapper(); + + private static final String VALUE = "value"; + private static final String TS = "ts"; + protected C config; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = loadGetAttributesNodeConfig(configuration); + mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false); + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); } protected abstract C loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException; @@ -61,6 +72,11 @@ public abstract class TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg); + private void safePutAttributes(TbContext ctx, TbMsg msg, T entityId) { if (entityId == null || entityId.isNullUid()) { ctx.tellNext(msg, FAILURE); @@ -106,15 +122,22 @@ public abstract class TbAbstractGetAttributesNode> latest = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), entityId, keys); return Futures.transform(latest, l -> { l.forEach(r -> { + boolean getLatestValueWithTs = BooleanUtils.toBooleanDefaultIfNull(this.config.isGetLatestValueWithTs(), false); if (BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { - if (r.getValue() != null) { - msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); - } else { + if (r.getValue() == null) { throw new RuntimeException("[" + r.getKey() + "] telemetry value is not present in the DB!"); + } else if (getLatestValueWithTs) { + putValueWithTs(msg, r); + } else { + msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); } } else { if (r.getValue() != null) { - msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); + if (getLatestValueWithTs) { + putValueWithTs(msg, r); + } else { + msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); + } } } }); @@ -122,10 +145,23 @@ public abstract class TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index 2b47b5264a..a9a5c47ba0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -34,6 +34,7 @@ public class TbGetAttributesNodeConfiguration implements NodeConfiguration latestTsKeyNames; private boolean tellFailureIfAbsent; + private boolean getLatestValueWithTs; @Override public TbGetAttributesNodeConfiguration defaultConfiguration() { @@ -43,6 +44,7 @@ public class TbGetAttributesNodeConfiguration implements NodeConfiguration Date: Fri, 22 Nov 2019 15:47:54 +0200 Subject: [PATCH 060/120] Optimization of Device Creation and Lookup Performance --- .../state/DefaultDeviceStateService.java | 48 ++++++++++--------- .../server/dao/asset/BaseAssetService.java | 45 ++++++++++++----- .../device/DeviceCredentialsServiceImpl.java | 35 ++++++++++---- .../server/dao/device/DeviceServiceImpl.java | 43 ++++++++++++----- .../dao/entity/AbstractEntityService.java | 25 ++++++++++ .../resources/sql/schema-entities-idx.sql | 12 +++++ .../main/resources/sql/schema-entities.sql | 9 ++-- 7 files changed, 160 insertions(+), 57 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index aee2444b2e..5c83a5f89b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -93,7 +94,8 @@ public class DefaultDeviceStateService implements DeviceStateService { public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime"; public static final String INACTIVITY_TIMEOUT = "inactivityTimeout"; - public static final List PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT); + public static final List PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, + LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT); @Autowired private TenantService tenantService; @@ -129,17 +131,11 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private boolean persistToTelemetry; -// TODO in v2.1 -// @Value("${state.defaultStatePersistenceIntervalInSec}") -// @Getter -// private long defaultStatePersistenceIntervalInSec; -// -// @Value("${state.defaultStatePersistencePack}") -// @Getter -// private long defaultStatePersistencePack; + @Value("${state.initFetchPackSize:1000}") + @Getter + private int initFetchPackSize; private ListeningScheduledExecutorService queueExecutor; - private ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); private ConcurrentMap deviceStates = new ConcurrentHashMap<>(); @@ -250,20 +246,28 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void initStateFromDB() { - List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); - for (Tenant tenant : tenants) { - List> fetchFutures = new ArrayList<>(); - List devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); - for (Device device : devices) { - if (!routingService.resolveById(device.getId()).isPresent()) { - fetchFutures.add(fetchDeviceState(device)); + try { + List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); + for (Tenant tenant : tenants) { + List> fetchFutures = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(initFetchPackSize); + while (pageLink != null) { + TextPageData page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); + pageLink = page.getNextPageLink(); + for (Device device : page.getData()) { + if (!routingService.resolveById(device.getId()).isPresent()) { + fetchFutures.add(fetchDeviceState(device)); + } + } + try { + Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to init device state service from DB", e); + } } } - try { - Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to init device state service from DB", e); - } + } catch (Throwable t) { + log.warn("Failed to init device states from DB", t); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index cf679a103f..8cd3c476f8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -20,6 +20,7 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -28,6 +29,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; @@ -113,7 +115,22 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ public Asset saveAsset(Asset asset) { log.trace("Executing saveAsset [{}]", asset); assetValidator.validate(asset, Asset::getTenantId); - return assetDao.save(asset.getTenantId(), asset); + Asset savedAsset; + if (!sqlDatabaseUsed) { + savedAsset = assetDao.save(asset.getTenantId(), asset); + } else { + try { + savedAsset = assetDao.save(asset.getTenantId(), asset); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("asset_name_unq_key")) { + throw new DataValidationException("Asset with such name already exists!"); + } else { + throw t; + } + } + } + return savedAsset; } @Override @@ -265,22 +282,26 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Override protected void validateCreate(TenantId tenantId, Asset asset) { - assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent( - d -> { - throw new DataValidationException("Asset with such name already exists!"); - } - ); + if (!sqlDatabaseUsed) { + assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent( + d -> { + throw new DataValidationException("Asset with such name already exists!"); + } + ); + } } @Override protected void validateUpdate(TenantId tenantId, Asset asset) { - assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent( - d -> { - if (!d.getId().equals(asset.getId())) { - throw new DataValidationException("Asset with such name already exists!"); + if (!sqlDatabaseUsed) { + assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent( + d -> { + if (!d.getId().equals(asset.getId())) { + throw new DataValidationException("Asset with such name already exists!"); + } } - } - ); + ); + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 7c7910ba8f..c14d25b65b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @@ -38,7 +40,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service @Slf4j -public class DeviceCredentialsServiceImpl implements DeviceCredentialsService { +public class DeviceCredentialsServiceImpl extends AbstractEntityService implements DeviceCredentialsService { @Autowired private DeviceCredentialsDao deviceCredentialsDao; @@ -78,7 +80,20 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService { } log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); credentialsValidator.validate(deviceCredentials, id -> tenantId); - return deviceCredentialsDao.save(tenantId, deviceCredentials); + if (!sqlDatabaseUsed) { + return deviceCredentialsDao.save(tenantId, deviceCredentials); + } else { + try { + return deviceCredentialsDao.save(tenantId, deviceCredentials); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_credentials_id_unq_key")) { + throw new DataValidationException("Specified credentials are already registered!"); + } else { + throw t; + } + } + } } private void formatCertData(DeviceCredentials deviceCredentials) { @@ -100,9 +115,11 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService { @Override protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) { - DeviceCredentials existingCredentialsEntity = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); - if (existingCredentialsEntity != null) { - throw new DataValidationException("Create of existent device credentials!"); + if (!sqlDatabaseUsed) { + DeviceCredentials existingCredentialsEntity = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); + if (existingCredentialsEntity != null) { + throw new DataValidationException("Create of existent device credentials!"); + } } } @@ -112,9 +129,11 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService { if (existingCredentials == null) { throw new DataValidationException("Unable to update non-existent device credentials!"); } - DeviceCredentials sameCredentialsId = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); - if (sameCredentialsId != null && !sameCredentialsId.getUuidId().equals(deviceCredentials.getUuidId())) { - throw new DataValidationException("Specified credentials are already registered!"); + if (!sqlDatabaseUsed) { + DeviceCredentials sameCredentialsId = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); + if (sameCredentialsId != null && !sameCredentialsId.getUuidId().equals(deviceCredentials.getUuidId())) { + throw new DataValidationException("Specified credentials are already registered!"); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 872e03a387..a26e275a96 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -123,7 +124,21 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe public Device saveDevice(Device device) { log.trace("Executing saveDevice [{}]", device); deviceValidator.validate(device, Device::getTenantId); - Device savedDevice = deviceDao.save(device.getTenantId(), device); + Device savedDevice; + if (!sqlDatabaseUsed) { + savedDevice = deviceDao.save(device.getTenantId(), device); + } else { + try { + savedDevice = deviceDao.save(device.getTenantId(), device); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { + throw new DataValidationException("Device with such name already exists!"); + } else { + throw t; + } + } + } if (device.getId() == null) { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); @@ -296,22 +311,26 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override protected void validateCreate(TenantId tenantId, Device device) { - deviceDao.findDeviceByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent( - d -> { - throw new DataValidationException("Device with such name already exists!"); - } - ); + if (!sqlDatabaseUsed) { + deviceDao.findDeviceByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent( + d -> { + throw new DataValidationException("Device with such name already exists!"); + } + ); + } } @Override protected void validateUpdate(TenantId tenantId, Device device) { - deviceDao.findDeviceByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent( - d -> { - if (!d.getUuidId().equals(device.getUuidId())) { - throw new DataValidationException("Device with such name already exists!"); + if (!sqlDatabaseUsed) { + deviceDao.findDeviceByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent( + d -> { + if (!d.getUuidId().equals(device.getUuidId())) { + throw new DataValidationException("Device with such name already exists!"); + } } - } - ); + ); + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index 5280637020..2fce1fd758 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -16,20 +16,45 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.relation.RelationService; +import javax.annotation.PostConstruct; +import java.util.Optional; + @Slf4j public abstract class AbstractEntityService { @Autowired protected RelationService relationService; + @Value("${database.entities.type:sql}") + private String databaseType; + + protected boolean sqlDatabaseUsed; + + @PostConstruct + public void init() { + sqlDatabaseUsed = "sql".equalsIgnoreCase(databaseType); + } + protected void deleteEntityRelations(TenantId tenantId, EntityId entityId) { log.trace("Executing deleteEntityRelations [{}]", entityId); relationService.deleteEntityRelations(tenantId, entityId); } + protected Optional extractConstraintViolationException(Exception t) { + if (t instanceof ConstraintViolationException) { + return Optional.of ((ConstraintViolationException) t); + } else if (t.getCause() instanceof ConstraintViolationException) { + return Optional.of ((ConstraintViolationException) (t.getCause())); + } else { + return Optional.empty(); + } + } + } diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 59785ae758..9809219ff9 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -21,3 +21,15 @@ CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_ty CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); + +CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); + +CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); + +CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); + +CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); + +CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); + +CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 089ed28afc..4b21d851a5 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -45,7 +45,8 @@ CREATE TABLE IF NOT EXISTS asset ( label varchar(255), search_text varchar(255), tenant_id varchar(31), - type varchar(255) + type varchar(255), + CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name) ); CREATE TABLE IF NOT EXISTS audit_log ( @@ -120,7 +121,8 @@ CREATE TABLE IF NOT EXISTS device ( name varchar(255), label varchar(255), search_text varchar(255), - tenant_id varchar(31) + tenant_id varchar(31), + CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name) ); CREATE TABLE IF NOT EXISTS device_credentials ( @@ -128,7 +130,8 @@ CREATE TABLE IF NOT EXISTS device_credentials ( credentials_id varchar, credentials_type varchar(255), credentials_value varchar, - device_id varchar(31) + device_id varchar(31), + CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id) ); CREATE TABLE IF NOT EXISTS event ( From 47e06f505ff3e9be41c3c5ed6203a62e070a8d32 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Fri, 22 Nov 2019 15:22:04 +0200 Subject: [PATCH 061/120] update the rulenode-core-config.js --- .../public/static/rulenode/rulenode-core-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index ac3a5c588a..60380956ab 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,6 +1,6 @@ !function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; -},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
tb.rulenode.limit-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'; -},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),$=n(60),k=i($),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),J=n(65),W=i(J);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",W.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}} -a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),$=n(103),k=i($);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity", -"check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
tb.rulenode.limit-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'; +},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),k=n(60),$=i(k),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),W=n(65),J=i(W);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",J.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}} +a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),k=n(103),$=i(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config($.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity", +"check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file From 6d2b856afef970ede7a7534c509af4303b02f31a Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Mon, 25 Nov 2019 18:53:15 +0200 Subject: [PATCH 062/120] Performance improvement --- .../transport/mqtt/MqttTransportHandler.java | 1 + .../dao/sql/device/DeviceRepository.java | 6 ++++++ .../server/dao/sql/device/JpaDeviceDao.java | 21 +++++++++++++------ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index d92b04e584..aa3757c4d7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -409,6 +409,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDisconnect(ChannelHandlerContext ctx) { ctx.close(); + log.info("[{}] Client disconnected!", sessionId); if (deviceSessionCtx.isConnected()) { transportService.process(sessionInfo, AbstractTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); transportService.deregisterSession(sessionInfo); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 1c61b33f82..3351bd2fea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -41,6 +41,12 @@ public interface DeviceRepository extends CrudRepository { @Param("idOffset") String idOffset, Pageable pageable); + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + + "AND d.id > :idOffset ORDER BY d.id") + List findByTenantId(@Param("tenantId") String tenantId, + @Param("idOffset") String idOffset, + Pageable pageable); + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + "AND d.id > :idOffset ORDER BY d.id") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index be0b4b89df..0355ca95f2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -65,12 +66,20 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao @Override public List findDevicesByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( - deviceRepository.findByTenantId( - fromTimeUUID(tenantId), - Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + if (StringUtils.isEmpty(pageLink.getTextSearch())) { + return DaoUtil.convertDataList( + deviceRepository.findByTenantId( + fromTimeUUID(tenantId), + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } else { + return DaoUtil.convertDataList( + deviceRepository.findByTenantId( + fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } } @Override From 81e6cccac9da1bd12c1120f58c7d5ff300b0d107 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Mon, 25 Nov 2019 19:41:25 +0200 Subject: [PATCH 063/120] Improved Device State Service load --- .../state/DefaultDeviceStateService.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 5c83a5f89b..1a57aa8dc3 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -223,24 +223,28 @@ public class DefaultDeviceStateService implements DeviceStateService { List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); for (Tenant tenant : tenants) { List> fetchFutures = new ArrayList<>(); - List devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); - for (Device device : devices) { - if (!routingService.resolveById(device.getId()).isPresent()) { - if (!deviceStates.containsKey(device.getId())) { - fetchFutures.add(fetchDeviceState(device)); - } - } else { - Set tenantDeviceSet = tenantDevices.get(tenant.getId()); - if (tenantDeviceSet != null) { - tenantDeviceSet.remove(device.getId()); + TextPageLink pageLink = new TextPageLink(initFetchPackSize); + while (pageLink != null) { + TextPageData page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); + pageLink = page.getNextPageLink(); + for (Device device : page.getData()) { + if (!routingService.resolveById(device.getId()).isPresent()) { + if (!deviceStates.containsKey(device.getId())) { + fetchFutures.add(fetchDeviceState(device)); + } + } else { + Set tenantDeviceSet = tenantDevices.get(tenant.getId()); + if (tenantDeviceSet != null) { + tenantDeviceSet.remove(device.getId()); + } + deviceStates.remove(device.getId()); } - deviceStates.remove(device.getId()); } - } - try { - Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to init device state service from DB", e); + try { + Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to init device state service from DB", e); + } } } } From 42a15097c65bfb2027da8b684f4db9fa3d8de651 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 12:49:11 +0200 Subject: [PATCH 064/120] Performance Improvement on start --- .../server/service/state/DefaultDeviceStateService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 1a57aa8dc3..094bab1e0d 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -135,6 +135,8 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private int initFetchPackSize; + private volatile boolean clusterUpdatePending = false; + private ListeningScheduledExecutorService queueExecutor; private ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); private ConcurrentMap deviceStates = new ConcurrentHashMap<>(); @@ -192,7 +194,10 @@ public class DefaultDeviceStateService implements DeviceStateService { @Override public void onClusterUpdate() { - queueExecutor.submit(this::onClusterUpdateSync); + if (!clusterUpdatePending) { + clusterUpdatePending = true; + queueExecutor.submit(this::onClusterUpdateSync); + } } @Override @@ -220,6 +225,7 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void onClusterUpdateSync() { + clusterUpdatePending = false; List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); for (Tenant tenant : tenants) { List> fetchFutures = new ArrayList<>(); From e18582f912cd09a42b6cf6343ba18a703e23a67e Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 13:22:52 +0200 Subject: [PATCH 065/120] Fixed device state service --- .../state/DefaultDeviceStateService.java | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 094bab1e0d..7f8ebf5057 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -39,7 +39,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.msg.TbMsg; @@ -51,6 +53,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; @@ -106,6 +109,9 @@ public class DefaultDeviceStateService implements DeviceStateService { @Autowired private AttributesService attributesService; + @Autowired + private TimeseriesService tsService; + @Autowired @Lazy private ActorService actorService; @@ -436,19 +442,28 @@ public class DefaultDeviceStateService implements DeviceStateService { } private ListenableFuture fetchDeviceState(Device device) { - ListenableFuture> attributes = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); - return Futures.transform(attributes, new Function, DeviceStateData>() { + if (persistToTelemetry) { + ListenableFuture> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); + return Futures.transform(tsData, extractDeviceStateData(device)); + } else { + ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); + return Futures.transform(attrData, extractDeviceStateData(device)); + } + } + + private Function, DeviceStateData> extractDeviceStateData(Device device) { + return new Function, DeviceStateData>() { @Nullable @Override - public DeviceStateData apply(@Nullable List attributes) { - long lastActivityTime = getAttributeValue(attributes, LAST_ACTIVITY_TIME, 0L); - long inactivityAlarmTime = getAttributeValue(attributes, INACTIVITY_ALARM_TIME, 0L); - long inactivityTimeout = getAttributeValue(attributes, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)); + public DeviceStateData apply(@Nullable List data) { + long lastActivityTime = getAttributeValue(data, LAST_ACTIVITY_TIME, 0L); + long inactivityAlarmTime = getAttributeValue(data, INACTIVITY_ALARM_TIME, 0L); + long inactivityTimeout = getAttributeValue(data, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)); boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout; DeviceState deviceState = DeviceState.builder() .active(active) - .lastConnectTime(getAttributeValue(attributes, LAST_CONNECT_TIME, 0L)) - .lastDisconnectTime(getAttributeValue(attributes, LAST_DISCONNECT_TIME, 0L)) + .lastConnectTime(getAttributeValue(data, LAST_CONNECT_TIME, 0L)) + .lastDisconnectTime(getAttributeValue(data, LAST_DISCONNECT_TIME, 0L)) .lastActivityTime(lastActivityTime) .lastInactivityAlarmTime(inactivityAlarmTime) .inactivityTimeout(inactivityTimeout) @@ -462,11 +477,11 @@ public class DefaultDeviceStateService implements DeviceStateService { .metaData(md) .state(deviceState).build(); } - }); + }; } - private long getAttributeValue(List attributes, String attributeName, long defaultValue) { - for (AttributeKvEntry attribute : attributes) { + private long getAttributeValue(List attributes, String attributeName, long defaultValue) { + for (KvEntry attribute : attributes) { if (attribute.getKey().equals(attributeName)) { return attribute.getLongValue().orElse(defaultValue); } From 7471ada0c541c174e7f5597fe50b4bfb00366df1 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 13:54:55 +0200 Subject: [PATCH 066/120] Fix device state fuction --- .../state/DefaultDeviceStateService.java | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 7f8ebf5057..1a1dc33bbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -456,34 +457,40 @@ public class DefaultDeviceStateService implements DeviceStateService { @Nullable @Override public DeviceStateData apply(@Nullable List data) { - long lastActivityTime = getAttributeValue(data, LAST_ACTIVITY_TIME, 0L); - long inactivityAlarmTime = getAttributeValue(data, INACTIVITY_ALARM_TIME, 0L); - long inactivityTimeout = getAttributeValue(data, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)); - boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout; - DeviceState deviceState = DeviceState.builder() - .active(active) - .lastConnectTime(getAttributeValue(data, LAST_CONNECT_TIME, 0L)) - .lastDisconnectTime(getAttributeValue(data, LAST_DISCONNECT_TIME, 0L)) - .lastActivityTime(lastActivityTime) - .lastInactivityAlarmTime(inactivityAlarmTime) - .inactivityTimeout(inactivityTimeout) - .build(); - TbMsgMetaData md = new TbMsgMetaData(); - md.putValue("deviceName", device.getName()); - md.putValue("deviceType", device.getType()); - return DeviceStateData.builder() - .tenantId(device.getTenantId()) - .deviceId(device.getId()) - .metaData(md) - .state(deviceState).build(); + try { + long lastActivityTime = getEntryValue(data, LAST_ACTIVITY_TIME, 0L); + long inactivityAlarmTime = getEntryValue(data, INACTIVITY_ALARM_TIME, 0L); + long inactivityTimeout = getEntryValue(data, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)); + boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout; + DeviceState deviceState = DeviceState.builder() + .active(active) + .lastConnectTime(getEntryValue(data, LAST_CONNECT_TIME, 0L)) + .lastDisconnectTime(getEntryValue(data, LAST_DISCONNECT_TIME, 0L)) + .lastActivityTime(lastActivityTime) + .lastInactivityAlarmTime(inactivityAlarmTime) + .inactivityTimeout(inactivityTimeout) + .build(); + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("deviceName", device.getName()); + md.putValue("deviceType", device.getType()); + return DeviceStateData.builder() + .tenantId(device.getTenantId()) + .deviceId(device.getId()) + .metaData(md) + .state(deviceState).build(); + } catch (Exception e) { + log.warn("[{}] Failed to fetch device state data", device.getId(), e); + } } }; } - private long getAttributeValue(List attributes, String attributeName, long defaultValue) { - for (KvEntry attribute : attributes) { - if (attribute.getKey().equals(attributeName)) { - return attribute.getLongValue().orElse(defaultValue); + private long getEntryValue(List kvEntries, String attributeName, long defaultValue) { + if (kvEntries != null) { + for (KvEntry entry : kvEntries) { + if (entry != null && !StringUtils.isEmpty(entry.getKey()) && entry.getKey().equals(attributeName)) { + return entry.getLongValue().orElse(defaultValue); + } } } return defaultValue; From bf4f389c4853589439c253d339d7b16c08da990a Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 14:01:46 +0200 Subject: [PATCH 067/120] Build fix --- .../server/service/state/DefaultDeviceStateService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 1a1dc33bbc..b42b4c6732 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -480,6 +480,7 @@ public class DefaultDeviceStateService implements DeviceStateService { .state(deviceState).build(); } catch (Exception e) { log.warn("[{}] Failed to fetch device state data", device.getId(), e); + throw new RuntimeException(e); } } }; From 61dcbb271f4c97f32a5de477b81c9bccd328ac60 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 26 Nov 2019 16:10:09 +0200 Subject: [PATCH 068/120] fixed constraint-violation-exception for ComponentDescriptor --- .../BaseComponentDescriptorService.java | 6 +- ...ctComponentDescriptorInsertRepository.java | 97 +++++++++++++++++++ ...qlComponentDescriptorInsertRepository.java | 50 ++++++++++ .../JpaBaseComponentDescriptorDao.java | 7 +- ...qlComponentDescriptorInsertRepository.java | 57 +++++++++++ 5 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java index b12e114d93..3886a9de65 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java @@ -54,11 +54,7 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic public ComponentDescriptor saveComponent(TenantId tenantId, ComponentDescriptor component) { componentValidator.validate(component, data -> new TenantId(EntityId.NULL_UUID)); Optional result = componentDescriptorDao.saveIfNotExist(tenantId, component); - if (result.isPresent()) { - return result.get(); - } else { - return componentDescriptorDao.findByClazz(tenantId, component.getClazz()); - } + return result.orElseGet(() -> componentDescriptorDao.findByClazz(tenantId, component.getClazz())); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java new file mode 100644 index 0000000000..ff464139b2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java @@ -0,0 +1,97 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.component; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; +import org.thingsboard.server.dao.util.SqlDao; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +@Slf4j +@SqlDao +@Repository +public abstract class AbstractComponentDescriptorInsertRepository { + + @PersistenceContext + protected EntityManager entityManager; + + @Autowired + protected PlatformTransactionManager transactionManager; + + public abstract ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity); + + protected ComponentDescriptorEntity saveAndGet(ComponentDescriptorEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) { + ComponentDescriptorEntity componentDescriptorEntity = null; + TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED); + try { + componentDescriptorEntity = processSaveOrUpdate(entity, insertOrUpdateOnPrimaryKeyConflict); + transactionManager.commit(insertTransaction); + } catch (Throwable throwable) { + transactionManager.rollback(insertTransaction); + if (throwable.getCause() instanceof ConstraintViolationException) { + log.trace("Insert request leaded in a violation of a defined integrity constraint {} for Component Descriptor with id {}, name {} and entityType {}", throwable.getMessage(), entity.getId(), entity.getName(), entity.getType()); + TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + try { + componentDescriptorEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); + } catch (Throwable th) { + log.trace("Could not execute the update statement for Component Descriptor with id {}, name {} and entityType {}", entity.getId(), entity.getName(), entity.getType()); + transactionManager.rollback(transaction); + } + transactionManager.commit(transaction); + } else { + log.trace("Could not execute the insert statement for Component Descriptor with id {}, name {} and entityType {}", entity.getId(), entity.getName(), entity.getType()); + } + } + return componentDescriptorEntity; + } + + @Modifying + protected abstract ComponentDescriptorEntity doProcessSaveOrUpdate(ComponentDescriptorEntity entity, String query); + + protected Query getQuery(ComponentDescriptorEntity entity, String query) { + return entityManager.createNativeQuery(query, ComponentDescriptorEntity.class) + .setParameter("id", UUIDConverter.fromTimeUUID(entity.getId())) + .setParameter("actions", entity.getActions()) + .setParameter("clazz", entity.getClazz()) + .setParameter("configuration_descriptor", entity.getConfigurationDescriptor().toString()) + .setParameter("name", entity.getName()) + .setParameter("scope", entity.getScope().name()) + .setParameter("search_text", entity.getSearchText()) + .setParameter("type", entity.getType().name()); + } + + private ComponentDescriptorEntity processSaveOrUpdate(ComponentDescriptorEntity entity, String query) { + return doProcessSaveOrUpdate(entity, query); + } + + private TransactionStatus getTransactionStatus(int propagationRequired) { + DefaultTransactionDefinition insertDefinition = new DefaultTransactionDefinition(); + insertDefinition.setPropagationBehavior(propagationRequired); + return transactionManager.getTransaction(insertDefinition); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java new file mode 100644 index 0000000000..ae7d10656c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.component; + +import org.springframework.stereotype.Repository; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +@SqlTsDao +@HsqlDao +@Repository +public class HsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository { + + private static final String P_KEY_CONFLICT_STATEMENT = "(component_descriptor.id=I.id)"; + private static final String UNQ_KEY_CONFLICT_STATEMENT = "(component_descriptor.clazz=I.clazz)"; + + private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertString(P_KEY_CONFLICT_STATEMENT); + private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertString(UNQ_KEY_CONFLICT_STATEMENT); + + @Override + public ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity) { + return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT); + } + + @Override + protected ComponentDescriptorEntity doProcessSaveOrUpdate(ComponentDescriptorEntity entity, String query) { + getQuery(entity, query).executeUpdate(); + return entityManager.find(ComponentDescriptorEntity.class, UUIDConverter.fromTimeUUID(entity.getId())); + } + + private static String getInsertString(String conflictStatement) { + return "MERGE INTO component_descriptor USING (VALUES :id, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type) I (id, actions, clazz, configuration_descriptor, name, scope, search_text, type) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET component_descriptor.id = I.id, component_descriptor.actions = I.actions, component_descriptor.clazz = I.clazz, component_descriptor.configuration_descriptor = I.configuration_descriptor, component_descriptor.name = I.name, component_descriptor.scope = I.scope, component_descriptor.search_text = I.search_text, component_descriptor.type = I.type" + + " WHEN NOT MATCHED THEN INSERT (id, actions, clazz, configuration_descriptor, name, scope, search_text, type) VALUES (I.id, I.actions, I.clazz, I.configuration_descriptor, I.name, I.scope, I.search_text, I.type)"; + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java index c66ea9c5cc..657c02d4a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java @@ -51,6 +51,9 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao getEntityClass() { return ComponentDescriptorEntity.class; @@ -67,7 +70,9 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao Date: Tue, 26 Nov 2019 16:16:25 +0200 Subject: [PATCH 069/120] license formated --- .../sql/component/PsqlComponentDescriptorInsertRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java index a12f2be884..80dcae31b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, From d40c054ca35386bb534b1c26f975017e6d07c8d7 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 16:48:08 +0200 Subject: [PATCH 070/120] Print Cassandra Queries with specified frequency --- .../src/main/resources/thingsboard.yml | 2 + .../nosql/CassandraBufferedRateExecutor.java | 5 ++- .../util/AbstractBufferedRateExecutor.java | 41 +++++++++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 5015ff090d..f6df3af742 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -183,6 +183,8 @@ cassandra: rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:10000}" # set all data types values except target to null for the same ts on save set_null_values_enabled: "${CASSANDRA_QUERY_SET_NULL_VALUES_ENABLED:false}" + # log one of cassandra queries with specified frequency (0 - logging is disabled) + print_queries_freq: "${CASSANDRA_QUERY_PRINT_FREQ:0}" tenant_rate_limits: enabled: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_ENABLED:false}" configuration: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION:1000:1,30000:60}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java index 12c2ee85f5..37aaa532fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java @@ -56,8 +56,9 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< @Value("${cassandra.query.poll_ms:50}") long pollMs, @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, - @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames) { - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration); + @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, + @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq) { + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq); this.printTenantNames = printTenantNames; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java index c22321ee52..a553aa5f9c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java @@ -44,6 +44,7 @@ public abstract class AbstractBufferedRateExecutor perTenantLimits = new ConcurrentHashMap<>(); @@ -57,12 +58,14 @@ public abstract class AbstractBufferedRateExecutor(queueLimit); this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads); this.callbackExecutor = Executors.newWorkStealingPool(callbackThreads); @@ -131,6 +134,13 @@ public abstract class AbstractBufferedRateExecutor finalTaskCtx = taskCtx; + if (printQueriesFreq > 0) { + if (printQueriesIdx.incrementAndGet() >= printQueriesFreq) { + printQueriesIdx.set(0); + String query = queryToString(finalTaskCtx); + log.info("[{}] Cassandra query: {}", taskCtx.getId(), query); + } + } logTask("Processing", finalTaskCtx); concurrencyLevel.incrementAndGet(); long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis(); @@ -187,17 +197,8 @@ public abstract class AbstractBufferedRateExecutor taskCtx) { if (log.isTraceEnabled()) { if (taskCtx.getTask() instanceof CassandraStatementTask) { - CassandraStatementTask cassStmtTask = (CassandraStatementTask) taskCtx.getTask(); - if (cassStmtTask.getStatement() instanceof BoundStatement) { - BoundStatement stmt = (BoundStatement) cassStmtTask.getStatement(); - String query = stmt.preparedStatement().getQueryString(); - try { - query = toStringWithValues(stmt, ProtocolVersion.V5); - } catch (Exception e) { - log.warn("Can't convert to query with values", e); - } - log.trace("[{}] {} task: {}, BoundStatement query: {}", taskCtx.getId(), action, taskCtx, query); - } + String query = queryToString(taskCtx); + log.trace("[{}] {} task: {}, BoundStatement query: {}", taskCtx.getId(), action, taskCtx, query); } else { log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx); } @@ -206,6 +207,22 @@ public abstract class AbstractBufferedRateExecutor taskCtx) { + CassandraStatementTask cassStmtTask = (CassandraStatementTask) taskCtx.getTask(); + if (cassStmtTask.getStatement() instanceof BoundStatement) { + BoundStatement stmt = (BoundStatement) cassStmtTask.getStatement(); + String query = stmt.preparedStatement().getQueryString(); + try { + query = toStringWithValues(stmt, ProtocolVersion.V5); + } catch (Exception e) { + log.warn("Can't convert to query with values", e); + } + return query; + } else { + return "Not Cassandra Statement Task"; + } + } + private static String toStringWithValues(BoundStatement boundStatement, ProtocolVersion protocolVersion) { CodecRegistry codecRegistry = boundStatement.preparedStatement().getCodecRegistry(); PreparedStatement preparedStatement = boundStatement.preparedStatement(); From 84e391963e819d84b9c9e385769074253be038ef Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 26 Nov 2019 17:15:36 +0200 Subject: [PATCH 071/120] Rule engine transport stats --- .../RemoteRuleEngineTransportService.java | 19 ++++- .../service/transport/RuleEngineStats.java | 80 +++++++++++++++++++ .../src/main/resources/thingsboard.yml | 3 + 3 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java index 4c0f9bdfee..1c1c7be483 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.transport; import akka.actor.ActorRef; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.BlockingBucket; import io.github.bucket4j.Bucket4j; @@ -30,11 +29,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -44,7 +42,6 @@ import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; import org.thingsboard.server.kafka.TBKafkaProducerTemplate; import org.thingsboard.server.kafka.TbKafkaSettings; import org.thingsboard.server.kafka.TbNodeIdProvider; -import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.encoding.DataDecodingEncodingService; @@ -83,6 +80,8 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ private long pollRecordsPerSecond; @Value("${transport.remote.rule_engine.max_poll_records_per_minute}") private long pollRecordsPerMinute; + @Value("${transport.remote.rule_engine.stats.enabled:false}") + private boolean statsEnabled; @Autowired private TbKafkaSettings kafkaSettings; @@ -108,6 +107,8 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ private volatile boolean stopped = false; + private final RuleEngineStats stats = new RuleEngineStats(); + @PostConstruct public void init() { TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder notificationsProducerBuilder = TBKafkaProducerTemplate.builder(); @@ -176,6 +177,13 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ }); } + @Scheduled(fixedDelayString = "${transport.remote.rule_engine.stats.print_interval_ms}") + public void printStats() { + if (statsEnabled) { + stats.printStats(); + } + } + @Override public void process(String nodeId, DeviceActorToTransportMsg msg) { process(nodeId, msg, null, null); @@ -191,6 +199,9 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ } private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) { + if (statsEnabled) { + stats.log(toDeviceActorMsg); + } TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); Optional address = routingService.resolveById(wrapper.getDeviceId()); if (address.isPresent()) { diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java new file mode 100644 index 0000000000..26a54a8548 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.transport; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class RuleEngineStats { + + private final AtomicInteger totalCounter = new AtomicInteger(0); + private final AtomicInteger sessionEventCounter = new AtomicInteger(0); + private final AtomicInteger postTelemetryCounter = new AtomicInteger(0); + private final AtomicInteger postAttributesCounter = new AtomicInteger(0); + private final AtomicInteger getAttributesCounter = new AtomicInteger(0); + private final AtomicInteger subscribeToAttributesCounter = new AtomicInteger(0); + private final AtomicInteger subscribeToRPCCounter = new AtomicInteger(0); + private final AtomicInteger toDeviceRPCCallResponseCounter = new AtomicInteger(0); + private final AtomicInteger toServerRPCCallRequestCounter = new AtomicInteger(0); + private final AtomicInteger subscriptionInfoCounter = new AtomicInteger(0); + private final AtomicInteger claimDeviceCounter = new AtomicInteger(0); + + public void log(TransportProtos.TransportToDeviceActorMsg msg) { + totalCounter.incrementAndGet(); + if (msg.hasSessionEvent()) { + sessionEventCounter.incrementAndGet(); + } + if (msg.hasPostTelemetry()) { + postTelemetryCounter.incrementAndGet(); + } + if (msg.hasPostAttributes()) { + postAttributesCounter.incrementAndGet(); + } + if (msg.hasGetAttributes()) { + getAttributesCounter.incrementAndGet(); + } + if (msg.hasSubscribeToAttributes()) { + subscribeToAttributesCounter.incrementAndGet(); + } + if (msg.hasSubscribeToRPC()) { + subscribeToRPCCounter.incrementAndGet(); + } + if (msg.hasToDeviceRPCCallResponse()) { + toDeviceRPCCallResponseCounter.incrementAndGet(); + } + if (msg.hasToServerRPCCallRequest()) { + toServerRPCCallRequestCounter.incrementAndGet(); + } + if (msg.hasSubscriptionInfo()) { + subscriptionInfoCounter.incrementAndGet(); + } + if (msg.hasClaimDevice()) { + claimDeviceCounter.incrementAndGet(); + } + } + + public void printStats() { + log.info("Transport total [{}] sessionEvents [{}] telemetry [{}] attributes [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] " + + "toServerRpc [{}] subInfo [{}] claimDevice [{}] ", + totalCounter.getAndSet(0), sessionEventCounter.getAndSet(0), postTelemetryCounter.getAndSet(0), + postAttributesCounter.getAndSet(0), getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0), + subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0), + toServerRPCCallRequestCounter.getAndSet(0), subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0)); + } +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f6df3af742..1e28eb5ef9 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -479,6 +479,9 @@ transport: poll_records_pack_size: "${TB_RULE_ENGINE_MAX_POLL_RECORDS:1000}" max_poll_records_per_second: "${TB_RULE_ENGINE_MAX_POLL_RECORDS_PER_SECOND:10000}" max_poll_records_per_minute: "${TB_RULE_ENGINE_MAX_POLL_RECORDS_PER_MINUTE:120000}" + stats: + enabled: "${TB_RULE_ENGINE_STATS_ENABLED:false}" + print_interval_ms: "${TB_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}" notifications: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" sessions: From 9004d716f319c6af627c75eab0d956eea866c56e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 26 Nov 2019 17:47:28 +0200 Subject: [PATCH 072/120] Fixed component insert repository --- ...ctComponentDescriptorInsertRepository.java | 8 ++----- .../ComponentDescriptorInsertRepository.java | 24 +++++++++++++++++++ ...qlComponentDescriptorInsertRepository.java | 5 ++-- .../JpaBaseComponentDescriptorDao.java | 2 +- ...qlComponentDescriptorInsertRepository.java | 5 ++-- 5 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorInsertRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java index ff464139b2..632dd869b9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java @@ -33,9 +33,7 @@ import javax.persistence.PersistenceContext; import javax.persistence.Query; @Slf4j -@SqlDao -@Repository -public abstract class AbstractComponentDescriptorInsertRepository { +public abstract class AbstractComponentDescriptorInsertRepository implements ComponentDescriptorInsertRepository { @PersistenceContext protected EntityManager entityManager; @@ -43,8 +41,6 @@ public abstract class AbstractComponentDescriptorInsertRepository { @Autowired protected PlatformTransactionManager transactionManager; - public abstract ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity); - protected ComponentDescriptorEntity saveAndGet(ComponentDescriptorEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) { ComponentDescriptorEntity componentDescriptorEntity = null; TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED); @@ -94,4 +90,4 @@ public abstract class AbstractComponentDescriptorInsertRepository { insertDefinition.setPropagationBehavior(propagationRequired); return transactionManager.getTransaction(insertDefinition); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorInsertRepository.java new file mode 100644 index 0000000000..dd24e38c11 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorInsertRepository.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.component; + +import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; + +public interface ComponentDescriptorInsertRepository { + + ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java index ae7d10656c..2297081eae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java @@ -19,9 +19,10 @@ import org.springframework.stereotype.Repository; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlTsDao; -@SqlTsDao +@SqlDao @HsqlDao @Repository public class HsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository { @@ -47,4 +48,4 @@ public class HsqlComponentDescriptorInsertRepository extends AbstractComponentDe return "MERGE INTO component_descriptor USING (VALUES :id, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type) I (id, actions, clazz, configuration_descriptor, name, scope, search_text, type) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET component_descriptor.id = I.id, component_descriptor.actions = I.actions, component_descriptor.clazz = I.clazz, component_descriptor.configuration_descriptor = I.configuration_descriptor, component_descriptor.name = I.name, component_descriptor.scope = I.scope, component_descriptor.search_text = I.search_text, component_descriptor.type = I.type" + " WHEN NOT MATCHED THEN INSERT (id, actions, clazz, configuration_descriptor, name, scope, search_text, type) VALUES (I.id, I.actions, I.clazz, I.configuration_descriptor, I.name, I.scope, I.search_text, I.type)"; } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java index 657c02d4a6..0b764eafd4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java @@ -52,7 +52,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao getEntityClass() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java index 80dcae31b6..260897ce01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java @@ -18,9 +18,10 @@ package org.thingsboard.server.dao.sql.component; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlTsDao; -@SqlTsDao +@SqlDao @PsqlDao @Repository public class PsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository { @@ -54,4 +55,4 @@ public class PsqlComponentDescriptorInsertRepository extends AbstractComponentDe private static String getUpdateStatement(String id) { return "actions = :actions, " + id + ", configuration_descriptor = :configuration_descriptor, name = :name, scope = :scope, search_text = :search_text, type = :type"; } -} \ No newline at end of file +} From efb0cb59ccff7725b6ae9e77f136b1841cf80d23 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 08:31:30 +0200 Subject: [PATCH 073/120] Improved Rest Client --- .../java/org/thingsboard/client/tools/RestClient.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 44f345dc96..6a07634699 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -27,6 +27,7 @@ import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; @@ -50,9 +51,9 @@ import java.util.Optional; @RequiredArgsConstructor public class RestClient implements ClientHttpRequestInterceptor { private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; - private final RestTemplate restTemplate = new RestTemplate(); + protected final RestTemplate restTemplate = new RestTemplate(); + protected final String baseURL; private String token; - private final String baseURL; public void login(String username, String password) { Map loginRequest = new HashMap<>(); @@ -202,6 +203,10 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate.postForEntity(baseURL + "/api/relation", relation, EntityRelation.class).getBody(); } + public Dashboard createDashboard(Dashboard dashboard) { + return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); + } + public DeviceCredentials getCredentials(DeviceId id) { return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); } From d430bb7a9902d702f2ef6d65060264349656cbde Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 11:31:34 +0200 Subject: [PATCH 074/120] Add REST API call to get dashboards --- .../thingsboard/client/tools/RestClient.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 6a07634699..55a5c4e501 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -17,6 +17,8 @@ package org.thingsboard.client.tools; import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -28,13 +30,16 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -42,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -207,6 +213,25 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); } + public void deleteDashboard(DashboardId dashboardId) { + restTemplate.delete(baseURL + "/api/dashboard/{dashboardId}", dashboardId); + } + + public List findTenantDashboards() { + try { + ResponseEntity> dashboards = + restTemplate.exchange(baseURL + "/api/tenant/dashboards?limit=100000", HttpMethod.GET, null, new ParameterizedTypeReference>() { + }); + return dashboards.getBody().getData(); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Collections.emptyList(); + } else { + throw exception; + } + } + } + public DeviceCredentials getCredentials(DeviceId id) { return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); } From d11d735493543a3850adfa2aee7d20ecb793c180 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 27 Nov 2019 13:13:11 +0200 Subject: [PATCH 075/120] Cleaned up code (#2210) * fixed constraint-violation-exception for ComponentDescriptor * license formated * cleaned code for events * license formated --- ...ctComponentDescriptorInsertRepository.java | 2 - ...qlComponentDescriptorInsertRepository.java | 1 - .../event/AbstractEventInsertRepository.java | 90 +++++++++++++++++++ .../dao/sql/event/EventInsertRepository.java | 78 +--------------- .../sql/event/HsqlEventInsertRepository.java | 2 +- .../sql/event/PsqlEventInsertRepository.java | 2 +- 6 files changed, 95 insertions(+), 80 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java index 632dd869b9..7bb98a9163 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java @@ -19,14 +19,12 @@ import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.Modifying; -import org.springframework.stereotype.Repository; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; -import org.thingsboard.server.dao.util.SqlDao; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java index 2297081eae..4ceb6cecc0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlDao; -import org.thingsboard.server.dao.util.SqlTsDao; @SqlDao @HsqlDao diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java new file mode 100644 index 0000000000..60201fcf82 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.event; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.dao.model.sql.EventEntity; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +@Slf4j +public abstract class AbstractEventInsertRepository implements EventInsertRepository { + + @PersistenceContext + protected EntityManager entityManager; + + @Autowired + protected PlatformTransactionManager transactionManager; + + protected EventEntity saveAndGet(EventEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) { + EventEntity eventEntity = null; + TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED); + try { + eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnPrimaryKeyConflict); + transactionManager.commit(insertTransaction); + } catch (Throwable throwable) { + transactionManager.rollback(insertTransaction); + if (throwable.getCause() instanceof ConstraintViolationException) { + log.trace("Insert request leaded in a violation of a defined integrity constraint {} for Entity with entityId {} and entityType {}", throwable.getMessage(), entity.getEventUid(), entity.getEventType()); + TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + try { + eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); + } catch (Throwable th) { + log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); + transactionManager.rollback(transaction); + } + transactionManager.commit(transaction); + } else { + log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); + } + } + return eventEntity; + } + + @Modifying + protected abstract EventEntity doProcessSaveOrUpdate(EventEntity entity, String query); + + protected Query getQuery(EventEntity entity, String query) { + return entityManager.createNativeQuery(query, EventEntity.class) + .setParameter("id", UUIDConverter.fromTimeUUID(entity.getId())) + .setParameter("body", entity.getBody().toString()) + .setParameter("entity_id", entity.getEntityId()) + .setParameter("entity_type", entity.getEntityType().name()) + .setParameter("event_type", entity.getEventType()) + .setParameter("event_uid", entity.getEventUid()) + .setParameter("tenant_id", entity.getTenantId()); + } + + private EventEntity processSaveOrUpdate(EventEntity entity, String query) { + return doProcessSaveOrUpdate(entity, query); + } + + private TransactionStatus getTransactionStatus(int propagationRequired) { + DefaultTransactionDefinition insertDefinition = new DefaultTransactionDefinition(); + insertDefinition.setPropagationBehavior(propagationRequired); + return transactionManager.getTransaction(insertDefinition); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java index 051453cf63..491c1fb8b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java @@ -15,82 +15,10 @@ */ package org.thingsboard.server.dao.sql.event; -import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.model.sql.EventEntity; -import org.thingsboard.server.dao.util.SqlDao; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; +public interface EventInsertRepository { -@Slf4j -@SqlDao -@Repository -public abstract class EventInsertRepository { + EventEntity saveOrUpdate(EventEntity entity); - @PersistenceContext - protected EntityManager entityManager; - - @Autowired - protected PlatformTransactionManager transactionManager; - - public abstract EventEntity saveOrUpdate(EventEntity entity); - - protected EventEntity saveAndGet(EventEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) { - EventEntity eventEntity = null; - TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED); - try { - eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnPrimaryKeyConflict); - transactionManager.commit(insertTransaction); - } catch (Throwable throwable) { - transactionManager.rollback(insertTransaction); - if (throwable.getCause() instanceof ConstraintViolationException) { - log.trace("Insert request leaded in a violation of a defined integrity constraint {} for Entity with entityId {} and entityType {}", throwable.getMessage(), entity.getEventUid(), entity.getEventType()); - TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - try { - eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); - } catch (Throwable th) { - log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); - transactionManager.rollback(transaction); - } - transactionManager.commit(transaction); - } else { - log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); - } - } - return eventEntity; - } - - @Modifying - protected abstract EventEntity doProcessSaveOrUpdate(EventEntity entity, String query); - - protected Query getQuery(EventEntity entity, String query) { - return entityManager.createNativeQuery(query, EventEntity.class) - .setParameter("id", UUIDConverter.fromTimeUUID(entity.getId())) - .setParameter("body", entity.getBody().toString()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("entity_type", entity.getEntityType().name()) - .setParameter("event_type", entity.getEventType()) - .setParameter("event_uid", entity.getEventUid()) - .setParameter("tenant_id", entity.getTenantId()); - } - - private EventEntity processSaveOrUpdate(EventEntity entity, String query) { - return doProcessSaveOrUpdate(entity, query); - } - - private TransactionStatus getTransactionStatus(int propagationRequired) { - DefaultTransactionDefinition insertDefinition = new DefaultTransactionDefinition(); - insertDefinition.setPropagationBehavior(propagationRequired); - return transactionManager.getTransaction(insertDefinition); - } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java index e34cc7b70c..54b3da2efc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java @@ -24,7 +24,7 @@ import org.thingsboard.server.dao.util.SqlDao; @SqlDao @HsqlDao @Repository -public class HsqlEventInsertRepository extends EventInsertRepository { +public class HsqlEventInsertRepository extends AbstractEventInsertRepository { private static final String P_KEY_CONFLICT_STATEMENT = "(event.id=I.id)"; private static final String UNQ_KEY_CONFLICT_STATEMENT = "(event.tenant_id=I.tenant_id AND event.entity_type=I.entity_type AND event.entity_id=I.entity_id AND event.event_type=I.event_type AND event.event_uid=I.event_uid)"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java index e4fd1ed37c..7e07e48983 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java @@ -25,7 +25,7 @@ import org.thingsboard.server.dao.util.SqlDao; @SqlDao @PsqlDao @Repository -public class PsqlEventInsertRepository extends EventInsertRepository { +public class PsqlEventInsertRepository extends AbstractEventInsertRepository { private static final String P_KEY_CONFLICT_STATEMENT = "(id)"; private static final String UNQ_KEY_CONFLICT_STATEMENT = "(tenant_id, entity_type, entity_id, event_type, event_uid)"; From 90d8bef576708bad98717f35e08913c3d1547bd9 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 18:38:55 +0200 Subject: [PATCH 076/120] Alarm performance improvements --- .../device/DeviceActorMessageProcessor.java | 10 +++-- .../src/main/resources/thingsboard.yml | 2 +- .../server/dao/alarm/BaseAlarmService.java | 37 +++++++------------ .../server/dao/sql/alarm/AlarmRepository.java | 7 +--- .../server/dao/sql/alarm/JpaAlarmDao.java | 4 +- .../resources/sql/schema-entities-idx.sql | 2 +- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index aa1f8bc909..6784a2a0f3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -223,6 +223,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } void process(ActorContext context, TransportToDeviceActorMsgWrapper wrapper) { + boolean reportDeviceActivity = false; TransportToDeviceActorMsg msg = wrapper.getMsg(); if (msg.hasSessionEvent()) { processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent()); @@ -235,11 +236,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } if (msg.hasPostAttributes()) { handlePostAttributesRequest(context, msg.getSessionInfo(), msg.getPostAttributes()); - reportLogicalDeviceActivity(); + reportDeviceActivity = true; } if (msg.hasPostTelemetry()) { handlePostTelemetryRequest(context, msg.getSessionInfo(), msg.getPostTelemetry()); - reportLogicalDeviceActivity(); + reportDeviceActivity = true; } if (msg.hasGetAttributes()) { handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes()); @@ -249,11 +250,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } if (msg.hasToServerRPCCallRequest()) { handleClientSideRPCRequest(context, msg.getSessionInfo(), msg.getToServerRPCCallRequest()); - reportLogicalDeviceActivity(); + reportDeviceActivity = true; } if (msg.hasSubscriptionInfo()) { handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo()); } + if (reportDeviceActivity) { + reportLogicalDeviceActivity(); + } } private void reportLogicalDeviceActivity() { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1e28eb5ef9..b181572247 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -215,7 +215,7 @@ actors: timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}" rule: # Specify thread pool size for database request callbacks executor service - db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:1}" + db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:50}" # Specify thread pool size for javascript executor service js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index d83b068cbe..8a4279fe7f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -52,6 +52,7 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -325,21 +326,21 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private AlarmSeverity detectHighestSeverity(List alarms) { if (!alarms.isEmpty()) { List sorted = new ArrayList(alarms); - sorted.sort((p1, p2) -> p1.getSeverity().compareTo(p2.getSeverity())); + sorted.sort(Comparator.comparing(Alarm::getSeverity)); return sorted.get(0).getSeverity(); } else { return null; } } - private void deleteRelation(TenantId tenantId, EntityRelation alarmRelation) throws ExecutionException, InterruptedException { + private void deleteRelation(TenantId tenantId, EntityRelation alarmRelation) { log.debug("Deleting Alarm relation: {}", alarmRelation); - relationService.deleteRelationAsync(tenantId, alarmRelation).get(); + relationService.deleteRelation(tenantId, alarmRelation); } - private void createRelation(TenantId tenantId, EntityRelation alarmRelation) throws ExecutionException, InterruptedException { + private void createRelation(TenantId tenantId, EntityRelation alarmRelation) { log.debug("Creating Alarm relation: {}", alarmRelation); - relationService.saveRelationAsync(tenantId, alarmRelation).get(); + relationService.saveRelation(tenantId, alarmRelation); } private Alarm merge(Alarm existing, Alarm alarm) { @@ -376,28 +377,18 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } private void createAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) { - try { - if (createAnyRelation) { - createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM)); - } - createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); - createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); - createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); - } catch (ExecutionException | InterruptedException e) { - log.warn("[{}] Failed to create relation. Status: [{}]", alarmId, status); - throw new RuntimeException(e); + if (createAnyRelation) { + createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM)); } + createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); + createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); + createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); } private void deleteAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status) { - try { - deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); - deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); - deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); - } catch (ExecutionException | InterruptedException e) { - log.warn("[{}] Failed to delete relation. Status: [{}]", alarmId, status); - throw new RuntimeException(e); - } + deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); + deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); + deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); } private void updateAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index a3d5b45a39..756ebbf070 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -31,11 +31,8 @@ import java.util.List; @SqlDao public interface AlarmRepository extends CrudRepository { - @Query("SELECT a FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.originatorId = :originatorId " + - "AND a.originatorType = :entityType AND a.type = :alarmType ORDER BY a.type ASC, a.id DESC") - List findLatestByOriginatorAndType(@Param("tenantId") String tenantId, - @Param("originatorId") String originatorId, - @Param("entityType") EntityType entityType, + @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY startTs DESC") + List findLatestByOriginatorAndType(@Param("originatorId") String originatorId, @Param("alarmType") String alarmType, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 4762c5c4ec..8aa9a81094 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -77,11 +77,9 @@ public class JpaAlarmDao extends JpaAbstractDao implements A public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { return service.submit(() -> { List latest = alarmRepository.findLatestByOriginatorAndType( - UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(originator.getId()), - originator.getEntityType(), type, - new PageRequest(0, 1)); + PageRequest.of(0, 1)); return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0)); }); } diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 9809219ff9..a74d45836d 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -14,7 +14,7 @@ -- limitations under the License. -- -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(tenant_id, type, originator_type, originator_id); +CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, startTs DESC); CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); From 3ae7434327849d798591effa894357d7ef489e83 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 19:30:58 +0200 Subject: [PATCH 077/120] Fixed index name --- dao/src/main/resources/sql/schema-entities-idx.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index a74d45836d..a0a25ee070 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -14,7 +14,7 @@ -- limitations under the License. -- -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, startTs DESC); +CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); From 423880ab096ece6a45aa3bbf281b61c4194d1665 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 19:44:34 +0200 Subject: [PATCH 078/120] RPC Actor Stats --- .../actors/service/DefaultActorService.java | 27 +++++++++++++++++++ .../src/main/resources/thingsboard.yml | 3 +++ 2 files changed, 30 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index bc28042fb2..9ac8f86590 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -22,8 +22,10 @@ import akka.actor.Terminated; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; @@ -50,6 +52,7 @@ import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.cluster.discovery.ServerInstance; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.transport.RuleEngineStats; import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; @@ -58,6 +61,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; @@ -187,8 +191,25 @@ public class DefaultActorService implements ActorService { this.rpcManagerActor.tell(msg, ActorRef.noSender()); } + @Value("${cluster.stats.enabled:false}") + private boolean statsEnabled; + + private final AtomicInteger sentClusterMsgs = new AtomicInteger(0); + private final AtomicInteger receivedClusterMsgs = new AtomicInteger(0); + + + @Scheduled(fixedDelayString = "${cluster.stats.print_interval_ms}") + public void printStats() { + if (statsEnabled) { + log.info("Cluster msgs sent [{}] received [{}]", sentClusterMsgs.getAndSet(0), receivedClusterMsgs.getAndSet(0)); + } + } + @Override public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) { + if (statsEnabled) { + receivedClusterMsgs.incrementAndGet(); + } ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort(), source.getServerType()); if (log.isDebugEnabled()) { log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress); @@ -239,11 +260,17 @@ public class DefaultActorService implements ActorService { @Override public void onSendMsg(ClusterAPIProtos.ClusterMessage msg) { + if (statsEnabled) { + sentClusterMsgs.incrementAndGet(); + } rpcManagerActor.tell(msg, ActorRef.noSender()); } @Override public void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg) { + if (statsEnabled) { + sentClusterMsgs.incrementAndGet(); + } rpcManagerActor.tell(msg, ActorRef.noSender()); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index b181572247..aa10b6c097 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -85,6 +85,9 @@ cluster: vitrual_nodes_size: "${CLUSTER_VIRTUAL_NODES_SIZE:16}" # Queue partition id for current node partition_id: "${QUEUE_PARTITION_ID:0}" + stats: + enabled: "${TB_RULE_ENGINE_STATS_ENABLED:false}" + print_interval_ms: "${TB_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}" # Plugins configuration parameters plugins: From 08bd89ef32172d8ee7d6f4b35824b44b3cd102e9 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 27 Nov 2019 19:56:54 +0200 Subject: [PATCH 079/120] Changes to yml file --- application/src/main/resources/thingsboard.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index aa10b6c097..764516b9c1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -86,8 +86,8 @@ cluster: # Queue partition id for current node partition_id: "${QUEUE_PARTITION_ID:0}" stats: - enabled: "${TB_RULE_ENGINE_STATS_ENABLED:false}" - print_interval_ms: "${TB_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}" + enabled: "${TB_CLUSTER_STATS_ENABLED:false}" + print_interval_ms: "${TB_CLUSTER_STATS_PRINT_INTERVAL_MS:10000}" # Plugins configuration parameters plugins: From 64a3baad4109909fdf2e6651f208d5505827be10 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Wed, 27 Nov 2019 16:58:48 +0200 Subject: [PATCH 080/120] remove null chars from str value of attributes and telemetry for sql --- .../src/main/resources/thingsboard.yml | 2 ++ .../AttributeKvInsertRepository.java | 22 +++++++++++++++---- .../dao/sqlts/AbstractInsertRepository.java | 15 +++++++++++++ .../timescale/TimescaleInsertRepository.java | 2 +- .../sqlts/ts/PsqlLatestInsertRepository.java | 2 +- .../ts/PsqlTimeseriesInsertRepository.java | 2 +- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 764516b9c1..8fd418a153 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -204,6 +204,8 @@ sql: batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:1000}" + # Specify whether to remove null characters from strValue of attributes and timeseries before insert + remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" # Actor system parameters actors: diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java index 89843048a0..0a537cbe01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.attributes; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.Modifying; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; @@ -34,12 +35,16 @@ import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; @SqlDao @Repository @Slf4j public abstract class AttributeKvInsertRepository { + private static final ThreadLocal PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE))); + private static final String EMPTY_STR = ""; + private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, last_update_ts = ? " + "WHERE entity_type = ? and entity_id = ? and attribute_type =? and attribute_key = ?;"; @@ -60,6 +65,9 @@ public abstract class AttributeKvInsertRepository { @Autowired private TransactionTemplate transactionTemplate; + @Value("${sql.remove_null_chars}") + private boolean removeNullChars; + @PersistenceContext protected EntityManager entityManager; @@ -99,7 +107,7 @@ public abstract class AttributeKvInsertRepository { .setParameter("entity_id", entity.getId().getEntityId()) .setParameter("attribute_type", entity.getId().getAttributeType()) .setParameter("attribute_key", entity.getId().getAttributeKey()) - .setParameter("str_v", entity.getStrValue()) + .setParameter("str_v", replaceNullChars(entity.getStrValue())) .setParameter("last_update_ts", entity.getLastUpdateTs()) .executeUpdate(); } @@ -135,7 +143,7 @@ public abstract class AttributeKvInsertRepository { int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { - ps.setString(1, entities.get(i).getStrValue()); + ps.setString(1, replaceNullChars(entities.get(i).getStrValue())); if (entities.get(i).getLongValue() != null) { ps.setLong(2, entities.get(i).getLongValue()); @@ -189,8 +197,8 @@ public abstract class AttributeKvInsertRepository { ps.setString(2, insertEntities.get(i).getId().getEntityId()); ps.setString(3, insertEntities.get(i).getId().getAttributeType()); ps.setString(4, insertEntities.get(i).getId().getAttributeKey()); - ps.setString(5, insertEntities.get(i).getStrValue()); - ps.setString(10, insertEntities.get(i).getStrValue()); + ps.setString(5, replaceNullChars(insertEntities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(insertEntities.get(i).getStrValue())); if (insertEntities.get(i).getLongValue() != null) { ps.setLong(6, insertEntities.get(i).getLongValue()); @@ -229,4 +237,10 @@ public abstract class AttributeKvInsertRepository { }); } + private String replaceNullChars(String strValue) { + if (removeNullChars && strValue != null) { + return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); + } + return strValue; + } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java index 274b07e4fc..919ab5314d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -15,14 +15,19 @@ */ package org.thingsboard.server.dao.sqlts; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import java.util.regex.Pattern; @Repository public abstract class AbstractInsertRepository { + private static final ThreadLocal PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE))); + private static final String EMPTY_STR = ""; + protected static final String BOOL_V = "bool_v"; protected static final String STR_V = "str_v"; protected static final String LONG_V = "long_v"; @@ -46,6 +51,9 @@ public abstract class AbstractInsertRepository { protected static final String PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS = "str_v = null, bool_v = null, dbl_v = null"; protected static final String PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, bool_v = null"; + @Value("${sql.remove_null_chars}") + private boolean removeNullChars; + @PersistenceContext protected EntityManager entityManager; @@ -71,4 +79,11 @@ public abstract class AbstractInsertRepository { throw new RuntimeException("Unsupported insert value: [" + notNullValue + "]"); } } + + protected String replaceNullChars(String strValue) { + if (removeNullChars) { + return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); + } + return strValue; + } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index d4cbd1c994..11f4ea4b5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -56,7 +56,7 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor .setParameter("entity_id", entity.getEntityId()) .setParameter("key", entity.getKey()) .setParameter("ts", entity.getTs()) - .setParameter("str_v", entity.getStrValue()) + .setParameter("str_v", replaceNullChars(entity.getStrValue())) .executeUpdate(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java index c61a74a15d..5d50bf0dd9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java @@ -58,7 +58,7 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { .setParameter("entity_id", entity.getEntityId()) .setParameter("key", entity.getKey()) .setParameter("ts", entity.getTs()) - .setParameter("str_v", entity.getStrValue()) + .setParameter("str_v", replaceNullChars(entity.getStrValue())) .executeUpdate(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java index 6390a7faee..0baea27d7b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java @@ -58,7 +58,7 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo .setParameter("entity_id", entity.getEntityId()) .setParameter("key", entity.getKey()) .setParameter("ts", entity.getTs()) - .setParameter("str_v", entity.getStrValue()) + .setParameter("str_v", replaceNullChars(entity.getStrValue())) .executeUpdate(); } From 5c972575bf936ccfc41300805e806048ba5a1e1d Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Wed, 27 Nov 2019 18:01:42 +0200 Subject: [PATCH 081/120] tests fix --- dao/src/test/resources/application-test.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 263da99a4b..caec9e0e66 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -40,3 +40,5 @@ security.claim.allowClaimingByDefault=true security.claim.duration=60000 database.ts_max_intervals=700 + +sql.remove_null_chars=true From 866d7c4a2ca72be75822fbfc81e74f2332194406 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Thu, 28 Nov 2019 14:59:09 +0200 Subject: [PATCH 082/120] [WIP] Feature/rest-client (#2208) * added methods from admin-controller, alarm-controller, asset-controller, audit-log-controller * refactored rest client and added methods from auth controller * added methods from component-descriptor-controller * added methods from customer controller * added methods from dashboard controller * added methods from device controller * refactored url pageLink params * added methods from entity relation controller * added methods from entity view controller * refactored * added methods from event controller * added methods from rpc controller * added methods from rule chain controller * added methods from telemetry controller * added methods from tenant controller * added methods from user controller * added methods from widgets bundle controller * added methods from widget type controller --- .../thingsboard/client/tools/RestClient.java | 1693 ++++++++++++++++- 1 file changed, 1682 insertions(+), 11 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 55a5c4e501..2d789ac706 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -18,6 +18,7 @@ package org.thingsboard.client.tools; import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -28,23 +29,47 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetSearchQuery; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationInfo; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.io.IOException; +import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -61,6 +86,16 @@ public class RestClient implements ClientHttpRequestInterceptor { protected final String baseURL; private String token; + private final static String TIME_PAGE_LINK_URL_PARAMS = "limit={limit}&startTime={startTime}&endTime={endTime}&ascOrder={ascOrder}&offset={offset}"; + private final static String TEXT_PAGE_LINK_URL_PARAMS = "limit={limit}&textSearch{textSearch}&idOffset={idOffset}&textOffset{textOffset}"; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { + HttpRequest wrapper = new HttpRequestWrapper(request); + wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); + return execution.execute(wrapper, bytes); + } + public void login(String username, String password) { Map loginRequest = new HashMap<>(); loginRequest.put("username", username); @@ -156,10 +191,6 @@ public class RestClient implements ClientHttpRequestInterceptor { return saveDeviceCredentials(deviceCredentials); } - public DeviceCredentials saveDeviceCredentials(DeviceCredentials deviceCredentials) { - return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody(); - } - public Device createDevice(Device device) { return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); } @@ -197,7 +228,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Asset assignAsset(CustomerId customerId, AssetId assetId) { - return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", HttpEntity.EMPTY, Asset.class, customerId.toString(), assetId.toString()).getBody(); } @@ -244,10 +275,1650 @@ public class RestClient implements ClientHttpRequestInterceptor { return token; } - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { - HttpRequest wrapper = new HttpRequestWrapper(request); - wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); - return execution.execute(wrapper, bytes); + public Optional getAdminSettings(String key) { + try { + ResponseEntity adminSettings = restTemplate.getForEntity(baseURL + "/api/admin/settings/{key}", AdminSettings.class, key); + return Optional.ofNullable(adminSettings.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public AdminSettings saveAdminSettings(AdminSettings adminSettings) { + return restTemplate.postForEntity(baseURL + "/api/settings", adminSettings, AdminSettings.class).getBody(); + } + + public void sendTestMail(AdminSettings adminSettings) { + restTemplate.postForEntity(baseURL + "/api/settings/testMail", adminSettings, AdminSettings.class); + } + + //TODO: +// @RequestMapping(value = "/securitySettings", method = RequestMethod.GET) +// public SecuritySettings getSecuritySettings() { +// +// } + //TODO: +// @RequestMapping(value = "/securitySettings", method = RequestMethod.POST) +// public SecuritySettings saveSecuritySettings(SecuritySettings securitySettings) { +// +// } + //TODO: +// @RequestMapping(value = "/updates", method = RequestMethod.GET) +// public UpdateMessage checkUpdates() { +// +// } + + public Optional getAlarmById(String alarmId) { + try { + ResponseEntity alarm = restTemplate.getForEntity(baseURL + "/api/alarm/{alarmId}", Alarm.class, alarmId); + return Optional.ofNullable(alarm.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getAlarmInfoById(String alarmId) { + try { + ResponseEntity alarmInfo = restTemplate.getForEntity(baseURL + "/api/alarm/info/{alarmId}", AlarmInfo.class, alarmId); + return Optional.ofNullable(alarmInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Alarm saveAlarm(Alarm alarm) { + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); + } + + public void deleteAlarm(String alarmId) { + restTemplate.delete(baseURL + "/api/alarm/{alarmId}", alarmId); + } + + public void ackAlarm(String alarmId) { + restTemplate.postForObject(baseURL + "/api/alarm/{alarmId}/ack", new Object(), Object.class, alarmId); + } + + public void clearAlarm(String alarmId) { + restTemplate.postForObject(baseURL + "/api/alarm/{alarmId}/clear", new Object(), Object.class, alarmId); + } + + public TimePageData getAlarms(String entityType, String entityId, String searchStatus, String status, TimePageLink pageLink, Boolean fetchOriginator) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("searchStatus", searchStatus); + params.put("status", status); + params.put("fetchOriginator", String.valueOf(fetchOriginator)); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/alarm/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}&fetchOriginator={fetchOriginator}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Optional getHighestAlarmSeverity(String entityType, String entityId, String searchStatus, String status) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("searchStatus", searchStatus); + params.put("status", status); + try { + ResponseEntity alarmSeverity = restTemplate.getForEntity(baseURL + "/api/alarm/highestSeverity/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}", AlarmSeverity.class, params); + return Optional.ofNullable(alarmSeverity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getAssetById(String assetId) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Asset saveAsset(Asset asset) { + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + public void deleteAsset(String assetId) { + restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId); + } + + public Optional assignAssetToCustomer(String customerId, + String assetId) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("assetId", assetId); + + try { + ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, params); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignAssetFromCustomer(String assetId) { + try { + ResponseEntity asset = restTemplate.exchange(baseURL + "/api/customer/asset/{assetId}", HttpMethod.DELETE, HttpEntity.EMPTY, Asset.class, assetId); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignAssetToPublicCustomer(String assetId) { + try { + ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/public/asset/{assetId}", null, Asset.class, assetId); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getTenantAssets(TextPageLink pageLink, String type) { + Map params = new HashMap<>(); + params.put("type", type); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/tenant/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + + public Optional getTenantAsset(String assetName) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerAssets(String customerId, TextPageLink pageLink, String type) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("type", type); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/customer/{customerId}/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + + public List getAssetsByIds(String[] assetIds) { + return restTemplate.exchange( + baseURL + "/api/assets?assetIds={assetIds}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + assetIds).getBody(); + } + + public List findByQuery(AssetSearchQuery query) { + return restTemplate.exchange( + URI.create(baseURL + "/api/assets"), + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getAssetTypes() { + return restTemplate.exchange(URI.create( + baseURL + "/api/asset/types"), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public TimePageData getAuditLogsByCustomerId(String customerId, TimePageLink pageLink, String actionTypes) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("actionTypes", actionTypes); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogsByUserId(String userId, TimePageLink pageLink, String actionTypes) { + Map params = new HashMap<>(); + params.put("userId", userId); + params.put("actionTypes", actionTypes); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/audit/logs/user/{userId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogsByEntityId(String entityType, String entityId, String actionTypes, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("actionTypes", actionTypes); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogs(TimePageLink pageLink, String actionTypes) { + Map params = new HashMap<>(); + params.put("actionTypes", actionTypes); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/audit/logs?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public Optional getUser() { + ResponseEntity user = restTemplate.getForEntity(baseURL + "/auth/user", User.class); + return Optional.ofNullable(user.getBody()); + } + + public void logout() { + restTemplate.exchange(URI.create(baseURL + "/auth/logout"), HttpMethod.POST, HttpEntity.EMPTY, Object.class); + } + + public void changePassword(JsonNode changePasswordRequest) { + restTemplate.exchange(URI.create(baseURL + "/auth/changePassword"), HttpMethod.POST, new HttpEntity<>(changePasswordRequest), Object.class); + } + + //TODO: +// @RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET) +// public UserPasswordPolicy getUserPasswordPolicy() { +// +// } + + + public ResponseEntity checkActivateToken(String activateToken) { + return restTemplate.getForEntity(baseURL + "/noauth/activate?activateToken={activateToken}", String.class, activateToken); + } + + public void requestResetPasswordByEmail(JsonNode resetPasswordByEmailRequest) { + restTemplate.exchange(URI.create(baseURL + "/noauth/resetPasswordByEmail"), HttpMethod.POST, new HttpEntity<>(resetPasswordByEmailRequest), Object.class); + } + + public ResponseEntity checkResetToken(String resetToken) { + return restTemplate.getForEntity(baseURL + "noauth/resetPassword?resetToken={resetToken}", String.class, resetToken); + } + + public Optional activateUser(JsonNode activateRequest) { + try { + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/noauth/activate", activateRequest, JsonNode.class); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional resetPassword(JsonNode resetPasswordRequest) { + try { + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/noauth/resetPassword", resetPasswordRequest, JsonNode.class); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getComponentDescriptorByClazz(String componentDescriptorClazz) { + try { + ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/component/{componentDescriptorClazz}", ComponentDescriptor.class); + return Optional.ofNullable(componentDescriptor.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public List getComponentDescriptorsByType(String componentType) { + return restTemplate.exchange( + baseURL + "/components?componentType={componentType}", + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + componentType).getBody(); + } + + public List getComponentDescriptorsByTypes(String[] componentTypes) { + return restTemplate.exchange( + baseURL + "/components?componentTypes={componentTypes}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + componentTypes).getBody(); + } + + public Optional getCustomerById(String customerId) { + try { + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/customer/{customerId}", Customer.class, customerId); + return Optional.ofNullable(customer.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getShortCustomerInfoById(String customerId) { + try { + ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/customer/{customerId}/shortInfo", JsonNode.class, customerId); + return Optional.ofNullable(customerInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public String getCustomerTitleById(String customerId) { + return restTemplate.getForObject(baseURL + "/customer/{customerId}/title", String.class, customerId); + } + + public Customer saveCustomer(Customer customer) { + return restTemplate.postForEntity(baseURL + "/customer", customer, Customer.class).getBody(); + } + + public void deleteCustomer(String customerId) { + restTemplate.delete(baseURL + "/customer/{customerId}", customerId); + } + + public TextPageData getCustomers(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + + ResponseEntity> customer = restTemplate.exchange( + baseURL + "/customers?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return customer.getBody(); + } + + public Optional getTenantCustomer(String customerTitle) { + try { + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/tenant/customers?customerTitle={customerTitle}", Customer.class, customerTitle); + return Optional.ofNullable(customer.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Long getServerTime() { + return restTemplate.getForObject(baseURL + "/dashboard/serverTime", Long.class); + } + + public Long getMaxDatapointsLimit() { + return restTemplate.getForObject(baseURL + "/dashboard/maxDatapointsLimit", Long.class); + } + + public Optional getDashboardInfoById(String dashboardId) { + try { + ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId); + return Optional.ofNullable(dashboardInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDashboardById(String dashboardId) { + try { + ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/dashboard/{dashboardId}", Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Dashboard saveDashboard(Dashboard dashboard) { + return restTemplate.postForEntity(baseURL + "/dashboard", dashboard, Dashboard.class).getBody(); + } + + public void deleteDashboard(String dashboardId) { + restTemplate.delete(baseURL + "/dashboard/{dashboardId}", dashboardId); + } + + public Optional assignDashboardToCustomer(String customerId, String dashboardId) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDashboardFromCustomer(String customerId, String dashboardId) { + try { + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/customer/{customerId}/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, customerId, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional updateDashboardCustomers(String dashboardId, String[] customerIds) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers", customerIds, Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional addDashboardCustomers(String dashboardId, String[] customerIds) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers/add", customerIds, Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional removeDashboardCustomers(String dashboardId, String[] customerIds) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers/remove", customerIds, Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignDashboardToPublicCustomer(String dashboardId) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDashboardFromPublicCustomer(String dashboardId) { + try { + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/customer/public/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, dashboardId); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getTenantDashboards(String tenantId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("tenantId", tenantId); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/tenant/{tenantId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params + ).getBody(); + } + + public TextPageData getTenantDashboards(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/tenant/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params + ).getBody(); + } + + public TimePageData getCustomerDashboards(String customerId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/customer/{customerId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params + ).getBody(); + } + + public Optional getDeviceById(String deviceId) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/device/{deviceId}", Device.class, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Device saveDevice(Device device) { + return restTemplate.postForEntity(baseURL + "/device", device, Device.class).getBody(); + } + + public void deleteDevice(String deviceId) { + restTemplate.delete(baseURL + "/device/{deviceId}", deviceId); + } + + public Optional assignDeviceToCustomer(String customerId, String deviceId) { + try { + ResponseEntity device = restTemplate.postForEntity(baseURL + "/customer/{customerId}/device/{deviceId}", null, Device.class, customerId, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDeviceFromCustomer(String deviceId) { + try { + ResponseEntity device = restTemplate.exchange(baseURL + "/customer/device/{deviceId}", HttpMethod.DELETE, HttpEntity.EMPTY, Device.class, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignDeviceToPublicCustomer(String deviceId) { + try { + ResponseEntity device = restTemplate.postForEntity(baseURL + "/customer/public/device/{deviceId}", null, Device.class, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDeviceCredentialsByDeviceId(String deviceId) { + try { + ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/device/{deviceId}/credentials", DeviceCredentials.class, deviceId); + return Optional.ofNullable(deviceCredentials.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public DeviceCredentials saveDeviceCredentials(DeviceCredentials deviceCredentials) { + return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody(); + } + + public TextPageData getTenantDevices(String type, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", type); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/tenant/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params) + .getBody(); + } + + public Optional getTenantDevice(String deviceName) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/tenant/devices?deviceName={deviceName}", Device.class, deviceName); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerDevices(String customerId, String type, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("type", type); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/customer/{customerId}/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params) + .getBody(); + } + + public List getDevicesByIds(String[] deviceIds) { + return restTemplate.exchange(baseURL + "/devices?deviceIds={deviceIds}", + HttpMethod.GET, + HttpEntity.EMPTY, new ParameterizedTypeReference>() { + }, + deviceIds).getBody(); + } + + public List findByQuery(DeviceSearchQuery query) { + return restTemplate.exchange( + baseURL + "/devices", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getDeviceTypes() { + return restTemplate.exchange( + baseURL + "/devices", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + //TODO: ClaimRequest class +// @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) +// public DeferredResult claimDevice(String deviceName, ClaimRequest claimRequest) { +// return restTemplate.exchange(baseURL + "/customer/device/{deviceName}/claim", HttpMethod.POST, new HttpEntity<>(claimRequest), new ParameterizedTypeReference>() { +// }, deviceName).getBody(); +// } + + public DeferredResult reClaimDevice(String deviceName) { + return restTemplate.exchange( + baseURL + "/customer/device/{deviceName}/claim", + HttpMethod.DELETE, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + deviceName).getBody(); + } + + public void saveRelation(EntityRelation relation) { + restTemplate.postForEntity(baseURL + "/relation", relation, Object.class); + } + + public void deleteRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { + Map params = new HashMap<>(); + params.put("fromId", fromId); + params.put("fromType", fromType); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup); + params.put("toId", toId); + params.put("toType", toType); + restTemplate.delete(baseURL + "/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", params); + } + + public void deleteRelations(String entityId, String entityType) { + restTemplate.delete(baseURL + "/relations?entityId={entityId}&entityType={entityType}", entityId, entityType); + } + + public Optional getRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { + Map params = new HashMap<>(); + params.put("fromId", fromId); + params.put("fromType", fromType); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup); + params.put("toId", toId); + params.put("toType", toType); + + try { + ResponseEntity entityRelation = restTemplate.getForEntity( + baseURL + "/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", + EntityRelation.class, + params); + return Optional.ofNullable(entityRelation.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public List findByFrom(String fromId, String fromType, String relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId); + params.put("fromType", fromType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findInfoByFrom(String fromId, String fromType, String relationTypeGroup) { + + Map params = new HashMap<>(); + params.put("fromId", fromId); + params.put("fromType", fromType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations/info?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByFrom(String fromId, String fromType, String relationType, String relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId); + params.put("fromType", fromType); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByTo(String toId, String toType, String relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId); + params.put("toType", toType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findInfoByTo(String toId, String toType, String relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId); + params.put("toType", toType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByTo(String toId, String toType, String relationType, String relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId); + params.put("toType", toType); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup); + + return restTemplate.exchange( + baseURL + "/relations?toId={toId}&toType={toType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByQuery(EntityRelationsQuery query) { + return restTemplate.exchange( + baseURL + "/relations", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List findInfoByQuery(EntityRelationsQuery query) { + return restTemplate.exchange( + baseURL + "/relations", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getEntityViewById(String entityViewId) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/entityView/{entityViewId}", EntityView.class, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public EntityView saveEntityView(EntityView entityView) { + return restTemplate.postForEntity(baseURL + "entityView", entityView, EntityView.class).getBody(); + } + + public void deleteEntityView(String entityViewId) { + restTemplate.delete(baseURL + "/entityView/{entityViewId}", entityViewId); + } + + public Optional getTenantEntityView(String entityViewName) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/tenant/entityViews?entityViewName={entityViewName}", EntityView.class, entityViewName); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignEntityViewToCustomer(String customerId, String entityViewId) { + try { + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignEntityViewFromCustomer(String entityViewId) { + try { + ResponseEntity entityView = restTemplate.exchange( + baseURL + "/customer/entityView/{entityViewId}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + EntityView.class, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerEntityViews(String customerId, String type, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + params.put("type", type); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/customer/{customerId}/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public TextPageData getTenantEntityViews(String type, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", type); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/tenant/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByQuery(EntityViewSearchQuery query) { + return restTemplate.exchange(baseURL + "/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getEntityViewTypes() { + return restTemplate.exchange(baseURL + "/entityView/types", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional assignEntityViewToPublicCustomer(String entityViewId) { + try { + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TimePageData getEvents(String entityType, String entityId, String eventType, String tenantId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("eventType", eventType); + params.put("tenantId", tenantId); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public TimePageData getEvents(String entityType, String entityId, String tenantId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("tenantId", tenantId); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/events/{entityType}/{entityId}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public DeferredResult handleOneWayDeviceRPCRequest(String deviceId, String requestBody) { + return restTemplate.exchange( + baseURL + "/oneway/{deviceId}", + HttpMethod.POST, + new HttpEntity<>(requestBody), + new ParameterizedTypeReference>() { + }, + deviceId).getBody(); + } + + public DeferredResult handleTwoWayDeviceRPCRequest(String deviceId, String requestBody) { + return restTemplate.exchange( + baseURL + "/twoway/{deviceId}", + HttpMethod.POST, + new HttpEntity<>(requestBody), + new ParameterizedTypeReference>() { + }, + deviceId).getBody(); + } + + public Optional getRuleChainById(String ruleChainId) { + try { + ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId); + return Optional.ofNullable(ruleChain.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getRuleChainMetaData(String ruleChainId) { + try { + ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId); + return Optional.ofNullable(ruleChainMetaData.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public RuleChain saveRuleChain(RuleChain ruleChain) { + return restTemplate.postForEntity(baseURL + "/ruleChain", ruleChain, RuleChain.class).getBody(); + } + + public Optional setRootRuleChain(String ruleChainId) { + try { + ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId); + return Optional.ofNullable(ruleChain.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { + return restTemplate.postForEntity(baseURL + "/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class).getBody(); + } + + public TextPageData getRuleChains(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/ruleChains" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + } + ).getBody(); + } + + public void deleteRuleChain(String ruleChainId) { + restTemplate.delete(baseURL + "/ruleChain/{ruleChainId}", ruleChainId); + } + + public Optional getLatestRuleNodeDebugInput(String ruleNodeId) { + try { + ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional testScript(JsonNode inputParams) { + try { + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/ruleChain/testScript", inputParams, JsonNode.class); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public DeferredResult getAttributeKeys(String entityType, String entityId) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/keys/attributes", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId).getBody(); + } + + public DeferredResult getAttributeKeysByScope(String entityType, String entityId, String scope) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/keys/attributes/{scope}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope).getBody(); + } + + public DeferredResult getAttributesResponseEntity(String entityType, String entityId, String keys) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/values/attributes?keys={keys}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + keys).getBody(); + } + + public DeferredResult getAttributesByScope(String entityType, String entityId, String scope, String keys) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope, + keys).getBody(); + } + + public DeferredResult getTimeseriesKeys(String entityType, String entityId) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/keys/timeseries", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId).getBody(); + } + + public DeferredResult getLatestTimeseries(String entityType, String entityId, String keys) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/values/timeseries?keys={keys}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + keys).getBody(); + } + + + public DeferredResult getTimeseries(String entityType, String entityId, String keys, Long startTs, Long endTs, Long interval, Integer limit, String agg) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("keys", keys); + params.put("startTs", startTs.toString()); + params.put("endTs", endTs.toString()); + params.put("interval", interval == null ? "0" : interval.toString()); + params.put("limit", limit == null ? "100" : limit.toString()); + params.put("agg", agg == null ? "NONE" : agg); + + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/values/timeseries?keys={keys}&startTs={startTs}&endTs={endTs}&interval={interval}&limit={limit}&agg={agg}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public DeferredResult saveDeviceAttributes(String deviceId, String scope, JsonNode request) { + return restTemplate.exchange( + baseURL + "/{deviceId}/{scope}", + HttpMethod.POST, + new HttpEntity<>(request), + new ParameterizedTypeReference>() { + }, + deviceId, + scope).getBody(); + } + + public DeferredResult saveEntityAttributesV1(String entityType, String entityId, String scope, JsonNode request) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/{scope}", + HttpMethod.POST, + new HttpEntity<>(request), + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope).getBody(); + } + + public DeferredResult saveEntityAttributesV2(String entityType, String entityId, String scope, JsonNode request) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/attributes/{scope}", + HttpMethod.POST, + new HttpEntity<>(request), + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope).getBody(); + } + + public DeferredResult saveEntityTelemetry(String entityType, String entityId, String scope, String requestBody) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/timeseries/{scope}", + HttpMethod.POST, + new HttpEntity<>(requestBody), + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope).getBody(); + } + + public DeferredResult saveEntityTelemetryWithTTL(String entityType, String entityId, String scope, Long ttl, String requestBody) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", + HttpMethod.POST, + new HttpEntity<>(requestBody), + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope, + ttl).getBody(); + } + + public DeferredResult deleteEntityTimeseries(String entityType, + String entityId, + String keys, + boolean deleteAllDataForKeys, + Long startTs, + Long endTs, + boolean rewriteLatestIfDeleted) { + Map params = new HashMap<>(); + params.put("entityType", entityType); + params.put("entityId", entityId); + params.put("keys", keys); + params.put("deleteAllDataForKeys", String.valueOf(deleteAllDataForKeys)); + params.put("startTs", startTs.toString()); + params.put("endTs", endTs.toString()); + params.put("rewriteLatestIfDeleted", String.valueOf(rewriteLatestIfDeleted)); + + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public DeferredResult deleteEntityAttributes(String deviceId, String scope, String keys) { + return restTemplate.exchange( + baseURL + "/{deviceId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + deviceId, + scope, + keys).getBody(); + } + + public DeferredResult deleteEntityAttributes(String entityType, String entityId, String scope, String keys) { + return restTemplate.exchange( + baseURL + "/{entityType}/{entityId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityType, + entityId, + scope, + keys).getBody(); + } + + public Optional getTenantById(String tenantId) { + try { + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/tenant/{tenantId}", Tenant.class, tenantId); + return Optional.ofNullable(tenant.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Tenant saveTenant(Tenant tenant) { + return restTemplate.postForEntity(baseURL + "/tenant", tenant, Tenant.class).getBody(); + } + + public void deleteTenant(String tenantId) { + restTemplate.delete(baseURL + "/tenant/{tenantId}", tenantId); + } + + public TextPageData getTenants(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/tenants?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public Optional getUserById(String userId) { + try { + ResponseEntity user = restTemplate.getForEntity(baseURL + "/user/{userId}", User.class, userId); + return Optional.ofNullable(user.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Boolean isUserTokenAccessEnabled() { + return restTemplate.getForEntity(baseURL + "/user/tokenAccessEnabled", Boolean.class).getBody(); + } + + public Optional getUserToken(String userId) { + try { + ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/user/{userId}/token", JsonNode.class, userId); + return Optional.ofNullable(userToken.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public User saveUser(User user, boolean sendActivationMail) { + return restTemplate.postForEntity(baseURL + "/user?sendActivationMail={sendActivationMail}", user, User.class, sendActivationMail).getBody(); + } + + public void sendActivationEmail(String email) { + restTemplate.postForEntity(baseURL + "/user/sendActivationMail?email={email}", null, Object.class, email); + } + + public Optional getActivationLink(String userId) { + try { + ResponseEntity activationLink = restTemplate.getForEntity(baseURL + "/user/{userId}/activationLink", String.class, userId); + return Optional.ofNullable(activationLink.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public void deleteUser(String userId) { + restTemplate.delete(baseURL + "/user/{userId}", userId); + } + + // @RequestMapping(value = "/tenant/{tenantId}/users", params = {"limit"}, method = RequestMethod.GET) + public TextPageData getTenantAdmins(String tenantId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("tenantId", tenantId); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/tenant/{tenantId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public TextPageData getCustomerUsers(String customerId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/customer/{customerId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public void setUserCredentialsEnabled(String userId, boolean userCredentialsEnabled) { + restTemplate.postForEntity( + baseURL + "/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", + null, + Object.class, + userId, + userCredentialsEnabled); + } + + public Optional getWidgetsBundleById(String widgetsBundleId) { + try { + ResponseEntity widgetsBundle = + restTemplate.getForEntity(baseURL + "/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId); + return Optional.ofNullable(widgetsBundle.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle) { + return restTemplate.postForEntity(baseURL + "/widgetsBundle", widgetsBundle, WidgetsBundle.class).getBody(); + } + + public void deleteWidgetsBundle(String widgetsBundleId) { + restTemplate.delete(baseURL + "/widgetsBundle/{widgetsBundleId}", widgetsBundleId); + } + + public TextPageData getWidgetsBundles(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/widgetsBundles?" + TEXT_PAGE_LINK_URL_PARAMS, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getWidgetsBundles() { + return restTemplate.exchange( + baseURL + "/widgetsBundles", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getWidgetTypeById(String widgetTypeId) { + try { + ResponseEntity widgetType = + restTemplate.getForEntity(baseURL + "/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId); + return Optional.ofNullable(widgetType.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public WidgetType saveWidgetType(WidgetType widgetType) { + return restTemplate.postForEntity(baseURL + "/widgetType", widgetType, WidgetType.class).getBody(); + } + + public void deleteWidgetType(String widgetTypeId) { + restTemplate.delete(baseURL + "/widgetType/{widgetTypeId}", widgetTypeId); + } + + public List getBundleWidgetTypes(boolean isSystem, String bundleAlias) { + return restTemplate.exchange( + baseURL + "/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + isSystem, + bundleAlias).getBody(); + } + + public Optional getWidgetType(boolean isSystem, String bundleAlias, String alias) { + try { + ResponseEntity widgetType = + restTemplate.getForEntity( + baseURL + "/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", + WidgetType.class, + isSystem, + bundleAlias, + alias); + return Optional.ofNullable(widgetType.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + private void addPageLinkToParam(Map params, TimePageLink pageLink) { + params.put("limit", String.valueOf(pageLink.getLimit())); + params.put("startTime", String.valueOf(pageLink.getStartTime())); + params.put("endTime", String.valueOf(pageLink.getEndTime())); + params.put("ascOrder", String.valueOf(pageLink.isAscOrder())); + params.put("offset", pageLink.getIdOffset().toString()); + } + + private void addPageLinkToParam(Map params, TextPageLink pageLink) { + params.put("limit", String.valueOf(pageLink.getLimit())); + params.put("textSearch", pageLink.getTextSearch()); + params.put("idOffset", pageLink.getIdOffset().toString()); + params.put("textOffset", pageLink.getTextOffset()); } -} \ No newline at end of file +} From 14d7f0af2f38e61681c60cbae7cedf32940498e7 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Thu, 28 Nov 2019 17:36:41 +0200 Subject: [PATCH 083/120] upgrade scripts fix --- .../main/data/upgrade/2.4.2/schema_update.sql | 31 +++++++++++++++++++ .../install/SqlDatabaseUpgradeService.java | 11 +++++++ 2 files changed, 42 insertions(+) create mode 100644 application/src/main/data/upgrade/2.4.2/schema_update.sql diff --git a/application/src/main/data/upgrade/2.4.2/schema_update.sql b/application/src/main/data/upgrade/2.4.2/schema_update.sql new file mode 100644 index 0000000000..79bbc91fec --- /dev/null +++ b/application/src/main/data/upgrade/2.4.2/schema_update.sql @@ -0,0 +1,31 @@ +-- +-- Copyright © 2016-2019 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; + +CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); + +CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); + +CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); + +CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); + +CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); + +CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); + +CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index e87fd0fce7..3210742388 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -182,6 +182,17 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { try { conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script } catch (Exception e) {} + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.2", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + try { + conn.createStatement().execute("ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) {} + try { + conn.createStatement().execute("ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) {} + try { + conn.createStatement().execute("ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) {} log.info("Schema updated."); } break; From 93a8c90882ea9c844d7d9698ff5d4ff03a81508d Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 28 Nov 2019 18:37:01 +0200 Subject: [PATCH 084/120] Improvement to device state service --- .../routing/ClusterRoutingService.java | 7 ------ .../ConsistentClusterRoutingService.java | 15 ------------ .../state/DefaultDeviceStateService.java | 23 ++++++++++++------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java index 425b578f7b..dabca5f3d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java @@ -30,13 +30,6 @@ public interface ClusterRoutingService extends DiscoveryServiceListener { ServerAddress getCurrentServer(); - Optional resolveByUuid(UUID uuid); - Optional resolveById(EntityId entityId); - Optional resolveByUuid(ServerType server, UUID uuid); - - Optional resolveById(ServerType server, EntityId entityId); - - } diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java index 87948eddba..99051389c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java @@ -88,21 +88,6 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService { return resolveByUuid(rootCircle, entityId.getId()); } - @Override - public Optional resolveByUuid(UUID uuid) { - return resolveByUuid(rootCircle, uuid); - } - - @Override - public Optional resolveByUuid(ServerType server, UUID uuid) { - return resolveByUuid(circles[server.ordinal()], uuid); - } - - @Override - public Optional resolveById(ServerType server, EntityId entityId) { - return resolveByUuid(circles[server.ordinal()], entityId.getId()); - } - private Optional resolveByUuid(ConsistentHashCircle circle, UUID uuid) { Assert.notNull(uuid); if (circle.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index b42b4c6732..e8336f2093 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -296,15 +296,21 @@ public class DefaultDeviceStateService implements DeviceStateService { private void updateState() { long ts = System.currentTimeMillis(); Set deviceIds = new HashSet<>(deviceStates.keySet()); + log.info("Calculating state updates for {} devices", deviceStates.size()); for (DeviceId deviceId : deviceIds) { - DeviceStateData stateData = deviceStates.get(deviceId); - DeviceState state = stateData.getState(); - state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { - state.setLastInactivityAlarmTime(ts); - pushRuleEngineMessage(stateData, INACTIVITY_EVENT); - save(deviceId, INACTIVITY_ALARM_TIME, ts); - save(deviceId, ACTIVITY_STATE, state.isActive()); + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); + if (stateData != null) { + DeviceState state = stateData.getState(); + state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { + state.setLastInactivityAlarmTime(ts); + pushRuleEngineMessage(stateData, INACTIVITY_EVENT); + save(deviceId, INACTIVITY_ALARM_TIME, ts); + save(deviceId, ACTIVITY_STATE, state.isActive()); + } + } else { + log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); + deviceStates.remove(deviceId); } } } @@ -353,6 +359,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (device != null) { try { deviceStateData = fetchDeviceState(device).get(); + deviceStates.putIfAbsent(deviceId, deviceStateData); } catch (InterruptedException | ExecutionException e) { log.debug("[{}] Failed to fetch device state!", deviceId, e); } From eb7c2f1659fea2a1b065c4d8a2eceeb630a16478 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Fri, 29 Nov 2019 13:48:31 +0200 Subject: [PATCH 085/120] Feature/rest client (#2218) * added methods from admin-controller, alarm-controller, asset-controller, audit-log-controller * refactored rest client and added methods from auth controller * added methods from component-descriptor-controller * added methods from customer controller * added methods from dashboard controller * added methods from device controller * refactored url pageLink params * added methods from entity relation controller * added methods from entity view controller * refactored * added methods from event controller * added methods from rpc controller * added methods from rule chain controller * added methods from telemetry controller * added methods from tenant controller * added methods from user controller * added methods from widgets bundle controller * added methods from widget type controller * created method refreshToken * moved classes SecuritySettings, UserPasswordPolicy, ClaimRequest, UpdateMessage, to common module, and added "/api" to urls where this part was missing --- .../server/controller/AdminController.java | 4 +- .../server/controller/AuthController.java | 7 +- .../server/controller/DeviceController.java | 2 +- .../system/DefaultSystemSecurityService.java | 5 +- .../system/SystemSecurityService.java | 2 +- .../service/update/DefaultUpdateService.java | 2 +- .../server/service/update/UpdateService.java | 2 +- .../server/common}/data/ClaimRequest.java | 2 +- .../server/common/data}/UpdateMessage.java | 2 +- .../security/model/SecuritySettings.java | 2 +- .../security/model/UserPasswordPolicy.java | 2 +- .../thingsboard/client/tools/RestClient.java | 335 ++++++++++-------- 12 files changed, 206 insertions(+), 161 deletions(-) rename {application/src/main/java/org/thingsboard/server/controller/claim => common/data/src/main/java/org/thingsboard/server/common}/data/ClaimRequest.java (92%) rename {application/src/main/java/org/thingsboard/server/service/update/model => common/data/src/main/java/org/thingsboard/server/common/data}/UpdateMessage.java (93%) rename {application/src/main/java/org/thingsboard/server/service => common/data/src/main/java/org/thingsboard/server/common/data}/security/model/SecuritySettings.java (93%) rename {application/src/main/java/org/thingsboard/server/service => common/data/src/main/java/org/thingsboard/server/common/data}/security/model/UserPasswordPolicy.java (94%) diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index a6122dd82e..81415ae35d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -28,12 +28,12 @@ import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.settings.AdminSettingsService; -import org.thingsboard.server.service.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.update.UpdateService; -import org.thingsboard.server.service.update.model.UpdateMessage; +import org.thingsboard.server.common.data.UpdateMessage; @RestController @RequestMapping("/api/admin") diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 690ba76a0f..f744f5d822 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -24,7 +24,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -43,14 +42,12 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; -import org.thingsboard.server.service.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import ua_parser.Client; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index f497c4fefa..698dcfb3f3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.claim.data.ClaimRequest; +import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.exception.IncorrectParameterException; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 94d0fea86f..3eb4d115e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -42,14 +42,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; -import org.thingsboard.server.service.security.model.SecuritySettings; -import org.thingsboard.server.service.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import javax.annotation.Resource; import java.util.ArrayList; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 50265863b3..1425600cc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -19,7 +19,7 @@ import org.springframework.security.core.AuthenticationException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.service.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.SecuritySettings; public interface SystemSecurityService { diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index 4a6fa6f271..14cbf95861 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import org.thingsboard.server.service.update.model.UpdateMessage; +import org.thingsboard.server.common.data.UpdateMessage; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; diff --git a/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java index 4ddc6dc3df..016c573399 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.service.update; -import org.thingsboard.server.service.update.model.UpdateMessage; +import org.thingsboard.server.common.data.UpdateMessage; public interface UpdateService { diff --git a/application/src/main/java/org/thingsboard/server/controller/claim/data/ClaimRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/ClaimRequest.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/controller/claim/data/ClaimRequest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ClaimRequest.java index 6620aa187e..8845edb872 100644 --- a/application/src/main/java/org/thingsboard/server/controller/claim/data/ClaimRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ClaimRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.controller.claim.data; +package org.thingsboard.server.common.data; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java rename to common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java index b4e61f7bc2..19a0341dfe 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.update.model; +package org.thingsboard.server.common.data; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecuritySettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/security/model/SecuritySettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java index bfc9176b4b..ee531fd4fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecuritySettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.security.model; +package org.thingsboard.server.common.data.security.model; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/UserPasswordPolicy.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/security/model/UserPasswordPolicy.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java index 0c50b5f6e0..9718c4ecb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/UserPasswordPolicy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.security.model; +package org.thingsboard.server.common.data.security.model; import lombok.Data; diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 2d789ac706..248a93fef1 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -31,6 +31,7 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; @@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -65,6 +67,8 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -85,6 +89,7 @@ public class RestClient implements ClientHttpRequestInterceptor { protected final RestTemplate restTemplate = new RestTemplate(); protected final String baseURL; private String token; + private String refreshToken; private final static String TIME_PAGE_LINK_URL_PARAMS = "limit={limit}&startTime={startTime}&endTime={endTime}&ascOrder={ascOrder}&offset={offset}"; private final static String TEXT_PAGE_LINK_URL_PARAMS = "limit={limit}&textSearch{textSearch}&idOffset={idOffset}&textOffset{textOffset}"; @@ -93,7 +98,23 @@ public class RestClient implements ClientHttpRequestInterceptor { public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { HttpRequest wrapper = new HttpRequestWrapper(request); wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); - return execution.execute(wrapper, bytes); + ClientHttpResponse response = execution.execute(wrapper, bytes); + if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { + synchronized (this) { + restTemplate.getInterceptors().remove(this); + refreshToken(); + wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); + return execution.execute(wrapper, bytes); + } + } + return response; + } + + public void refreshToken() { + Map refreshTokenRequest = new HashMap<>(); + refreshTokenRequest.put("refreshToken", refreshToken); + ResponseEntity tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/token", refreshTokenRequest, JsonNode.class); + setTokenInfo(tokenInfo.getBody()); } public void login(String username, String password) { @@ -101,8 +122,13 @@ public class RestClient implements ClientHttpRequestInterceptor { loginRequest.put("username", username); loginRequest.put("password", password); ResponseEntity tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/login", loginRequest, JsonNode.class); - this.token = tokenInfo.getBody().get("token").asText(); - restTemplate.setInterceptors(Collections.singletonList(this)); + setTokenInfo(tokenInfo.getBody()); + } + + private void setTokenInfo(JsonNode tokenInfo) { + this.token = tokenInfo.get("token").asText(); + this.refreshToken = tokenInfo.get("refreshToken").asText(); + restTemplate.getInterceptors().add(this); } public Optional findDevice(String name) { @@ -289,28 +315,42 @@ public class RestClient implements ClientHttpRequestInterceptor { } public AdminSettings saveAdminSettings(AdminSettings adminSettings) { - return restTemplate.postForEntity(baseURL + "/api/settings", adminSettings, AdminSettings.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/admin/settings", adminSettings, AdminSettings.class).getBody(); } public void sendTestMail(AdminSettings adminSettings) { - restTemplate.postForEntity(baseURL + "/api/settings/testMail", adminSettings, AdminSettings.class); - } - - //TODO: -// @RequestMapping(value = "/securitySettings", method = RequestMethod.GET) -// public SecuritySettings getSecuritySettings() { -// -// } - //TODO: -// @RequestMapping(value = "/securitySettings", method = RequestMethod.POST) -// public SecuritySettings saveSecuritySettings(SecuritySettings securitySettings) { -// -// } - //TODO: -// @RequestMapping(value = "/updates", method = RequestMethod.GET) -// public UpdateMessage checkUpdates() { -// -// } + restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); + } + + public Optional getSecuritySettings() { + try { + ResponseEntity securitySettings = restTemplate.getForEntity(baseURL + "/api/admin/securitySettings", SecuritySettings.class); + return Optional.ofNullable(securitySettings.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public SecuritySettings saveSecuritySettings(SecuritySettings securitySettings) { + return restTemplate.postForEntity(baseURL + "/api/admin/securitySettings", securitySettings, SecuritySettings.class).getBody(); + } + + public Optional checkUpdates() { + try { + ResponseEntity updateMsg = restTemplate.getForEntity(baseURL + "/api/admin/updates", UpdateMessage.class); + return Optional.ofNullable(updateMsg.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } public Optional getAlarmById(String alarmId) { try { @@ -488,7 +528,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( - baseURL + "/customer/{customerId}/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -532,7 +572,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -548,7 +588,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/audit/logs/user/{userId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/user/{userId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -565,7 +605,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -580,7 +620,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/audit/logs?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -590,40 +630,47 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Optional getUser() { - ResponseEntity user = restTemplate.getForEntity(baseURL + "/auth/user", User.class); + ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/auth/user", User.class); return Optional.ofNullable(user.getBody()); } public void logout() { - restTemplate.exchange(URI.create(baseURL + "/auth/logout"), HttpMethod.POST, HttpEntity.EMPTY, Object.class); + restTemplate.exchange(URI.create(baseURL + "/api/auth/logout"), HttpMethod.POST, HttpEntity.EMPTY, Object.class); } public void changePassword(JsonNode changePasswordRequest) { - restTemplate.exchange(URI.create(baseURL + "/auth/changePassword"), HttpMethod.POST, new HttpEntity<>(changePasswordRequest), Object.class); + restTemplate.exchange(URI.create(baseURL + "/api/auth/changePassword"), HttpMethod.POST, new HttpEntity<>(changePasswordRequest), Object.class); } - //TODO: -// @RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET) -// public UserPasswordPolicy getUserPasswordPolicy() { -// -// } + public Optional getUserPasswordPolicy() { + try { + ResponseEntity userPasswordPolicy = restTemplate.getForEntity(baseURL + "/api/noauth/userPasswordPolicy", UserPasswordPolicy.class); + return Optional.ofNullable(userPasswordPolicy.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } public ResponseEntity checkActivateToken(String activateToken) { - return restTemplate.getForEntity(baseURL + "/noauth/activate?activateToken={activateToken}", String.class, activateToken); + return restTemplate.getForEntity(baseURL + "/api/noauth/activate?activateToken={activateToken}", String.class, activateToken); } public void requestResetPasswordByEmail(JsonNode resetPasswordByEmailRequest) { - restTemplate.exchange(URI.create(baseURL + "/noauth/resetPasswordByEmail"), HttpMethod.POST, new HttpEntity<>(resetPasswordByEmailRequest), Object.class); + restTemplate.exchange(URI.create(baseURL + "/api/noauth/resetPasswordByEmail"), HttpMethod.POST, new HttpEntity<>(resetPasswordByEmailRequest), Object.class); } public ResponseEntity checkResetToken(String resetToken) { - return restTemplate.getForEntity(baseURL + "noauth/resetPassword?resetToken={resetToken}", String.class, resetToken); + return restTemplate.getForEntity(baseURL + "/api/noauth/resetPassword?resetToken={resetToken}", String.class, resetToken); } public Optional activateUser(JsonNode activateRequest) { try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/noauth/activate", activateRequest, JsonNode.class); + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -636,7 +683,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional resetPassword(JsonNode resetPasswordRequest) { try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/noauth/resetPassword", resetPasswordRequest, JsonNode.class); + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/resetPassword", resetPasswordRequest, JsonNode.class); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -649,7 +696,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getComponentDescriptorByClazz(String componentDescriptorClazz) { try { - ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/component/{componentDescriptorClazz}", ComponentDescriptor.class); + ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/api/component/{componentDescriptorClazz}", ComponentDescriptor.class); return Optional.ofNullable(componentDescriptor.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -662,7 +709,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List getComponentDescriptorsByType(String componentType) { return restTemplate.exchange( - baseURL + "/components?componentType={componentType}", + baseURL + "/api/components?componentType={componentType}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -671,7 +718,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List getComponentDescriptorsByTypes(String[] componentTypes) { return restTemplate.exchange( - baseURL + "/components?componentTypes={componentTypes}", + baseURL + "/api/components?componentTypes={componentTypes}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -681,7 +728,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getCustomerById(String customerId) { try { - ResponseEntity customer = restTemplate.getForEntity(baseURL + "/customer/{customerId}", Customer.class, customerId); + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}", Customer.class, customerId); return Optional.ofNullable(customer.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -694,7 +741,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getShortCustomerInfoById(String customerId) { try { - ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/customer/{customerId}/shortInfo", JsonNode.class, customerId); + ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}/shortInfo", JsonNode.class, customerId); return Optional.ofNullable(customerInfo.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -706,15 +753,15 @@ public class RestClient implements ClientHttpRequestInterceptor { } public String getCustomerTitleById(String customerId) { - return restTemplate.getForObject(baseURL + "/customer/{customerId}/title", String.class, customerId); + return restTemplate.getForObject(baseURL + "/api/customer/{customerId}/title", String.class, customerId); } public Customer saveCustomer(Customer customer) { - return restTemplate.postForEntity(baseURL + "/customer", customer, Customer.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); } public void deleteCustomer(String customerId) { - restTemplate.delete(baseURL + "/customer/{customerId}", customerId); + restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId); } public TextPageData getCustomers(TextPageLink pageLink) { @@ -722,7 +769,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> customer = restTemplate.exchange( - baseURL + "/customers?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customers?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -733,7 +780,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getTenantCustomer(String customerTitle) { try { - ResponseEntity customer = restTemplate.getForEntity(baseURL + "/tenant/customers?customerTitle={customerTitle}", Customer.class, customerTitle); + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/tenant/customers?customerTitle={customerTitle}", Customer.class, customerTitle); return Optional.ofNullable(customer.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -745,16 +792,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Long getServerTime() { - return restTemplate.getForObject(baseURL + "/dashboard/serverTime", Long.class); + return restTemplate.getForObject(baseURL + "/api/dashboard/serverTime", Long.class); } public Long getMaxDatapointsLimit() { - return restTemplate.getForObject(baseURL + "/dashboard/maxDatapointsLimit", Long.class); + return restTemplate.getForObject(baseURL + "/api/dashboard/maxDatapointsLimit", Long.class); } public Optional getDashboardInfoById(String dashboardId) { try { - ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId); + ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/api/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId); return Optional.ofNullable(dashboardInfo.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -767,7 +814,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getDashboardById(String dashboardId) { try { - ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/dashboard/{dashboardId}", Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/api/dashboard/{dashboardId}", Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -779,16 +826,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Dashboard saveDashboard(Dashboard dashboard) { - return restTemplate.postForEntity(baseURL + "/dashboard", dashboard, Dashboard.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); } public void deleteDashboard(String dashboardId) { - restTemplate.delete(baseURL + "/dashboard/{dashboardId}", dashboardId); + restTemplate.delete(baseURL + "/api/dashboard/{dashboardId}", dashboardId); } public Optional assignDashboardToCustomer(String customerId, String dashboardId) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -801,7 +848,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional unassignDashboardFromCustomer(String customerId, String dashboardId) { try { - ResponseEntity dashboard = restTemplate.exchange(baseURL + "/customer/{customerId}/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, customerId, dashboardId); + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, customerId, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -814,7 +861,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional updateDashboardCustomers(String dashboardId, String[] customerIds) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -827,7 +874,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional addDashboardCustomers(String dashboardId, String[] customerIds) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers/add", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/add", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -840,7 +887,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional removeDashboardCustomers(String dashboardId, String[] customerIds) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/dashboard/{dashboardId}/customers/remove", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/remove", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -853,7 +900,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional assignDashboardToPublicCustomer(String dashboardId) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -866,7 +913,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional unassignDashboardFromPublicCustomer(String dashboardId) { try { - ResponseEntity dashboard = restTemplate.exchange(baseURL + "/customer/public/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/customer/public/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -882,7 +929,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("tenantId", tenantId); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenant/{tenantId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/{tenantId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -894,7 +941,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenant/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -907,7 +954,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("customerId", customerId); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/customer/{customerId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -917,7 +964,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getDeviceById(String deviceId) { try { - ResponseEntity device = restTemplate.getForEntity(baseURL + "/device/{deviceId}", Device.class, deviceId); + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}", Device.class, deviceId); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -929,16 +976,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Device saveDevice(Device device) { - return restTemplate.postForEntity(baseURL + "/device", device, Device.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); } public void deleteDevice(String deviceId) { - restTemplate.delete(baseURL + "/device/{deviceId}", deviceId); + restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId); } public Optional assignDeviceToCustomer(String customerId, String deviceId) { try { - ResponseEntity device = restTemplate.postForEntity(baseURL + "/customer/{customerId}/device/{deviceId}", null, Device.class, customerId, deviceId); + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId, deviceId); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -951,7 +998,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional unassignDeviceFromCustomer(String deviceId) { try { - ResponseEntity device = restTemplate.exchange(baseURL + "/customer/device/{deviceId}", HttpMethod.DELETE, HttpEntity.EMPTY, Device.class, deviceId); + ResponseEntity device = restTemplate.exchange(baseURL + "/api/customer/device/{deviceId}", HttpMethod.DELETE, HttpEntity.EMPTY, Device.class, deviceId); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -964,7 +1011,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional assignDeviceToPublicCustomer(String deviceId) { try { - ResponseEntity device = restTemplate.postForEntity(baseURL + "/customer/public/device/{deviceId}", null, Device.class, deviceId); + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/public/device/{deviceId}", null, Device.class, deviceId); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -977,7 +1024,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getDeviceCredentialsByDeviceId(String deviceId) { try { - ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/device/{deviceId}/credentials", DeviceCredentials.class, deviceId); + ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}/credentials", DeviceCredentials.class, deviceId); return Optional.ofNullable(deviceCredentials.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -997,7 +1044,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenant/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1007,7 +1054,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getTenantDevice(String deviceName) { try { - ResponseEntity device = restTemplate.getForEntity(baseURL + "/tenant/devices?deviceName={deviceName}", Device.class, deviceName); + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1024,7 +1071,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/customer/{customerId}/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1033,7 +1080,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } public List getDevicesByIds(String[] deviceIds) { - return restTemplate.exchange(baseURL + "/devices?deviceIds={deviceIds}", + return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1042,7 +1089,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List findByQuery(DeviceSearchQuery query) { return restTemplate.exchange( - baseURL + "/devices", + baseURL + "/api/devices", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { @@ -1051,23 +1098,26 @@ public class RestClient implements ClientHttpRequestInterceptor { public List getDeviceTypes() { return restTemplate.exchange( - baseURL + "/devices", + baseURL + "/api/devices", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }).getBody(); } - //TODO: ClaimRequest class -// @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) -// public DeferredResult claimDevice(String deviceName, ClaimRequest claimRequest) { -// return restTemplate.exchange(baseURL + "/customer/device/{deviceName}/claim", HttpMethod.POST, new HttpEntity<>(claimRequest), new ParameterizedTypeReference>() { -// }, deviceName).getBody(); -// } + public DeferredResult claimDevice(String deviceName, ClaimRequest claimRequest) { + return restTemplate.exchange( + baseURL + "/api/customer/device/{deviceName}/claim", + HttpMethod.POST, + new HttpEntity<>(claimRequest), + new ParameterizedTypeReference>() { + }, + deviceName).getBody(); + } public DeferredResult reClaimDevice(String deviceName) { return restTemplate.exchange( - baseURL + "/customer/device/{deviceName}/claim", + baseURL + "/api/customer/device/{deviceName}/claim", HttpMethod.DELETE, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1076,7 +1126,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void saveRelation(EntityRelation relation) { - restTemplate.postForEntity(baseURL + "/relation", relation, Object.class); + restTemplate.postForEntity(baseURL + "/api/relation", relation, Object.class); } public void deleteRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { @@ -1087,11 +1137,11 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); params.put("toId", toId); params.put("toType", toType); - restTemplate.delete(baseURL + "/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", params); + restTemplate.delete(baseURL + "/api/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", params); } public void deleteRelations(String entityId, String entityType) { - restTemplate.delete(baseURL + "/relations?entityId={entityId}&entityType={entityType}", entityId, entityType); + restTemplate.delete(baseURL + "/api/relations?entityId={entityId}&entityType={entityType}", entityId, entityType); } public Optional getRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { @@ -1105,7 +1155,7 @@ public class RestClient implements ClientHttpRequestInterceptor { try { ResponseEntity entityRelation = restTemplate.getForEntity( - baseURL + "/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", + baseURL + "/api/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", EntityRelation.class, params); return Optional.ofNullable(entityRelation.getBody()); @@ -1125,7 +1175,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1134,14 +1184,13 @@ public class RestClient implements ClientHttpRequestInterceptor { } public List findInfoByFrom(String fromId, String fromType, String relationTypeGroup) { - Map params = new HashMap<>(); params.put("fromId", fromId); params.put("fromType", fromType); params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations/info?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations/info?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1157,7 +1206,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1172,7 +1221,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1187,7 +1236,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1203,7 +1252,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("relationTypeGroup", relationTypeGroup); return restTemplate.exchange( - baseURL + "/relations?toId={toId}&toType={toType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations?toId={toId}&toType={toType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1213,7 +1262,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List findByQuery(EntityRelationsQuery query) { return restTemplate.exchange( - baseURL + "/relations", + baseURL + "/api/relations", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { @@ -1222,7 +1271,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List findInfoByQuery(EntityRelationsQuery query) { return restTemplate.exchange( - baseURL + "/relations", + baseURL + "/api/relations", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { @@ -1231,7 +1280,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getEntityViewById(String entityViewId) { try { - ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/entityView/{entityViewId}", EntityView.class, entityViewId); + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/{entityViewId}", EntityView.class, entityViewId); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1243,16 +1292,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public EntityView saveEntityView(EntityView entityView) { - return restTemplate.postForEntity(baseURL + "entityView", entityView, EntityView.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); } public void deleteEntityView(String entityViewId) { - restTemplate.delete(baseURL + "/entityView/{entityViewId}", entityViewId); + restTemplate.delete(baseURL + "/api/entityView/{entityViewId}", entityViewId); } public Optional getTenantEntityView(String entityViewName) { try { - ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/tenant/entityViews?entityViewName={entityViewName}", EntityView.class, entityViewName); + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/tenant/entityViews?entityViewName={entityViewName}", EntityView.class, entityViewName); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1265,7 +1314,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional assignEntityViewToCustomer(String customerId, String entityViewId) { try { - ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId, entityViewId); + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId, entityViewId); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1279,7 +1328,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional unassignEntityViewFromCustomer(String entityViewId) { try { ResponseEntity entityView = restTemplate.exchange( - baseURL + "/customer/entityView/{entityViewId}", + baseURL + "/api/customer/entityView/{entityViewId}", HttpMethod.DELETE, HttpEntity.EMPTY, EntityView.class, entityViewId); @@ -1299,7 +1348,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/customer/{customerId}/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1312,7 +1361,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenant/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1321,18 +1370,18 @@ public class RestClient implements ClientHttpRequestInterceptor { } public List findByQuery(EntityViewSearchQuery query) { - return restTemplate.exchange(baseURL + "/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { + return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { }).getBody(); } public List getEntityViewTypes() { - return restTemplate.exchange(baseURL + "/entityView/types", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { + return restTemplate.exchange(baseURL + "/api/entityView/types", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }).getBody(); } public Optional assignEntityViewToPublicCustomer(String entityViewId) { try { - ResponseEntity entityView = restTemplate.postForEntity(baseURL + "customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId); + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1352,7 +1401,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1368,7 +1417,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/events/{entityType}/{entityId}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/events/{entityType}/{entityId}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1398,7 +1447,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getRuleChainById(String ruleChainId) { try { - ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId); + ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId); return Optional.ofNullable(ruleChain.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1411,7 +1460,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getRuleChainMetaData(String ruleChainId) { try { - ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId); + ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId); return Optional.ofNullable(ruleChainMetaData.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1423,12 +1472,12 @@ public class RestClient implements ClientHttpRequestInterceptor { } public RuleChain saveRuleChain(RuleChain ruleChain) { - return restTemplate.postForEntity(baseURL + "/ruleChain", ruleChain, RuleChain.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); } public Optional setRootRuleChain(String ruleChainId) { try { - ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId); + ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId); return Optional.ofNullable(ruleChain.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1440,14 +1489,14 @@ public class RestClient implements ClientHttpRequestInterceptor { } public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { - return restTemplate.postForEntity(baseURL + "/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class).getBody(); } public TextPageData getRuleChains(TextPageLink pageLink) { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/ruleChains" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/ruleChains" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1456,12 +1505,12 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void deleteRuleChain(String ruleChainId) { - restTemplate.delete(baseURL + "/ruleChain/{ruleChainId}", ruleChainId); + restTemplate.delete(baseURL + "/api/ruleChain/{ruleChainId}", ruleChainId); } public Optional getLatestRuleNodeDebugInput(String ruleNodeId) { try { - ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId); + ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/api/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1474,7 +1523,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional testScript(JsonNode inputParams) { try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/ruleChain/testScript", inputParams, JsonNode.class); + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/ruleChain/testScript", inputParams, JsonNode.class); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1689,7 +1738,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getTenantById(String tenantId) { try { - ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/tenant/{tenantId}", Tenant.class, tenantId); + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/{tenantId}", Tenant.class, tenantId); return Optional.ofNullable(tenant.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1701,11 +1750,11 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Tenant saveTenant(Tenant tenant) { - return restTemplate.postForEntity(baseURL + "/tenant", tenant, Tenant.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); } public void deleteTenant(String tenantId) { - restTemplate.delete(baseURL + "/tenant/{tenantId}", tenantId); + restTemplate.delete(baseURL + "/api/tenant/{tenantId}", tenantId); } public TextPageData getTenants(TextPageLink pageLink) { @@ -1722,7 +1771,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getUserById(String userId) { try { - ResponseEntity user = restTemplate.getForEntity(baseURL + "/user/{userId}", User.class, userId); + ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId); return Optional.ofNullable(user.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1734,12 +1783,12 @@ public class RestClient implements ClientHttpRequestInterceptor { } public Boolean isUserTokenAccessEnabled() { - return restTemplate.getForEntity(baseURL + "/user/tokenAccessEnabled", Boolean.class).getBody(); + return restTemplate.getForEntity(baseURL + "/api/user/tokenAccessEnabled", Boolean.class).getBody(); } public Optional getUserToken(String userId) { try { - ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/user/{userId}/token", JsonNode.class, userId); + ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/api/user/{userId}/token", JsonNode.class, userId); return Optional.ofNullable(userToken.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1751,16 +1800,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public User saveUser(User user, boolean sendActivationMail) { - return restTemplate.postForEntity(baseURL + "/user?sendActivationMail={sendActivationMail}", user, User.class, sendActivationMail).getBody(); + return restTemplate.postForEntity(baseURL + "/api/user?sendActivationMail={sendActivationMail}", user, User.class, sendActivationMail).getBody(); } public void sendActivationEmail(String email) { - restTemplate.postForEntity(baseURL + "/user/sendActivationMail?email={email}", null, Object.class, email); + restTemplate.postForEntity(baseURL + "/api/user/sendActivationMail?email={email}", null, Object.class, email); } public Optional getActivationLink(String userId) { try { - ResponseEntity activationLink = restTemplate.getForEntity(baseURL + "/user/{userId}/activationLink", String.class, userId); + ResponseEntity activationLink = restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId); return Optional.ofNullable(activationLink.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1772,7 +1821,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void deleteUser(String userId) { - restTemplate.delete(baseURL + "/user/{userId}", userId); + restTemplate.delete(baseURL + "/api/user/{userId}", userId); } // @RequestMapping(value = "/tenant/{tenantId}/users", params = {"limit"}, method = RequestMethod.GET) @@ -1796,7 +1845,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/customer/{customerId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1806,7 +1855,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public void setUserCredentialsEnabled(String userId, boolean userCredentialsEnabled) { restTemplate.postForEntity( - baseURL + "/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", + baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", null, Object.class, userId, @@ -1816,7 +1865,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getWidgetsBundleById(String widgetsBundleId) { try { ResponseEntity widgetsBundle = - restTemplate.getForEntity(baseURL + "/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId); + restTemplate.getForEntity(baseURL + "/api/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId); return Optional.ofNullable(widgetsBundle.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1828,18 +1877,18 @@ public class RestClient implements ClientHttpRequestInterceptor { } public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle) { - return restTemplate.postForEntity(baseURL + "/widgetsBundle", widgetsBundle, WidgetsBundle.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/widgetsBundle", widgetsBundle, WidgetsBundle.class).getBody(); } public void deleteWidgetsBundle(String widgetsBundleId) { - restTemplate.delete(baseURL + "/widgetsBundle/{widgetsBundleId}", widgetsBundleId); + restTemplate.delete(baseURL + "/api/widgetsBundle/{widgetsBundleId}", widgetsBundleId); } public TextPageData getWidgetsBundles(TextPageLink pageLink) { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/widgetsBundles?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/widgetsBundles?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1848,7 +1897,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public List getWidgetsBundles() { return restTemplate.exchange( - baseURL + "/widgetsBundles", + baseURL + "/api/widgetsBundles", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1858,7 +1907,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getWidgetTypeById(String widgetTypeId) { try { ResponseEntity widgetType = - restTemplate.getForEntity(baseURL + "/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId); + restTemplate.getForEntity(baseURL + "/api/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId); return Optional.ofNullable(widgetType.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1870,16 +1919,16 @@ public class RestClient implements ClientHttpRequestInterceptor { } public WidgetType saveWidgetType(WidgetType widgetType) { - return restTemplate.postForEntity(baseURL + "/widgetType", widgetType, WidgetType.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/widgetType", widgetType, WidgetType.class).getBody(); } public void deleteWidgetType(String widgetTypeId) { - restTemplate.delete(baseURL + "/widgetType/{widgetTypeId}", widgetTypeId); + restTemplate.delete(baseURL + "/api/widgetType/{widgetTypeId}", widgetTypeId); } public List getBundleWidgetTypes(boolean isSystem, String bundleAlias) { return restTemplate.exchange( - baseURL + "/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", + baseURL + "/api/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1892,7 +1941,7 @@ public class RestClient implements ClientHttpRequestInterceptor { try { ResponseEntity widgetType = restTemplate.getForEntity( - baseURL + "/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", + baseURL + "/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", WidgetType.class, isSystem, bundleAlias, From 85b63c5bbb15157ee214caadd43975e110652acf Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Fri, 29 Nov 2019 13:49:20 +0200 Subject: [PATCH 086/120] rest api call node generated ui (#2206) --- .../public/static/rulenode/rulenode-core-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 60380956ab..2ed9e929b1 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,6 +1,6 @@ !function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; -},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
tb.rulenode.limit-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'; -},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),k=n(60),$=i(k),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),W=n(65),J=i(W);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",J.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}} -a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),k=n(103),$=i(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config($.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity", -"check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.read-timeout-hint
tb.rulenode.max-parallel-requests-count-hint
tb.rulenode.headers-hint
{{ \'tb.rulenode.use-redis-queue\' | translate }}
{{ \'tb.rulenode.trim-redis-queue\' | translate }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
tb.rulenode.limit-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'; +},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
{{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}
tb.rulenode.get-latest-value-with-ts-hint
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),x=i(q),k=n(60),$=i(k),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),S=i(N),_=n(62),E=i(_),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),H=i(z),B=n(70),G=i(B),Y=n(58),Q=i(Y),W=n(65),J=i(W);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",x.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",H.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",J.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"], +Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),x=i(q),k=n(103),$=i(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",x.default).config($.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata", +"delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{TITLE:{name:"tb.rulenode.entity-details-title",value:"TITLE"},COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file From b25ea1e6136b0a6b45b047f0ec5b663c3787ff42 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Mon, 25 Nov 2019 15:19:18 +0200 Subject: [PATCH 087/120] rest api call node added parallel processing logic --- .../rule/engine/rest/TbHttpClient.java | 54 ++++++++++++++++--- .../rest/TbRestApiCallNodeConfiguration.java | 4 ++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index a3e119f588..5c0bf9d942 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.Netty4ClientHttpRequestFactory; import org.springframework.util.concurrent.ListenableFuture; @@ -37,6 +38,8 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.net.ssl.SSLException; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; @Data @@ -50,21 +53,24 @@ class TbHttpClient { private static final String ERROR_BODY = "error_body"; private final TbRestApiCallNodeConfiguration config; - private final boolean useRedisQueueForMsgPersistence; private EventLoopGroup eventLoopGroup; private AsyncRestTemplate httpClient; + private Deque>> pendingFutures; TbHttpClient(TbRestApiCallNodeConfiguration config) throws TbNodeException { try { this.config = config; - this.useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence(); + if (config.getMaxParallelRequestsCount() > 0) { + pendingFutures = new ConcurrentLinkedDeque<>(); + } if (config.isUseSimpleClientHttpFactory()) { httpClient = new AsyncRestTemplate(); } else { this.eventLoopGroup = new NioEventLoopGroup(); Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); nettyFactory.setSslContext(SslContextBuilder.forClient().build()); + nettyFactory.setReadTimeout(config.getReadTimeoutMs()); httpClient = new AsyncRestTemplate(nettyFactory); } } catch (SSLException e) { @@ -89,8 +95,12 @@ class TbHttpClient { future.addCallback(new ListenableFutureCallback>() { @Override public void onFailure(Throwable throwable) { - if (useRedisQueueForMsgPersistence) { - queueProcessor.pushOnFailure(msg); + if (config.isUseRedisQueueForMsgPersistence()) { + if (throwable instanceof HttpClientErrorException) { + processHttpClientError(((HttpClientErrorException) throwable).getStatusCode(), msg, queueProcessor); + } else { + queueProcessor.pushOnFailure(msg); + } } TbMsg next = processException(ctx, msg, throwable); ctx.tellFailure(next, throwable); @@ -99,20 +109,23 @@ class TbHttpClient { @Override public void onSuccess(ResponseEntity responseEntity) { if (responseEntity.getStatusCode().is2xxSuccessful()) { - if (useRedisQueueForMsgPersistence) { + if (config.isUseRedisQueueForMsgPersistence()) { queueProcessor.resetCounter(); } TbMsg next = processResponse(ctx, msg, responseEntity); ctx.tellNext(next, TbRelationTypes.SUCCESS); } else { - if (useRedisQueueForMsgPersistence) { - queueProcessor.pushOnFailure(msg); + if (config.isUseRedisQueueForMsgPersistence()) { + processHttpClientError(responseEntity.getStatusCode(), msg, queueProcessor); } TbMsg next = processFailureResponse(ctx, msg, responseEntity); ctx.tellNext(next, TbRelationTypes.FAILURE); } } }); + if (pendingFutures != null) { + processParallelRequests(future); + } } private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { @@ -150,4 +163,31 @@ class TbHttpClient { config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData))); return headers; } + + private void processParallelRequests(ListenableFuture> future) { + pendingFutures.add(future); + if (pendingFutures.size() > config.getMaxParallelRequestsCount()) { + for (int i = 0; i < config.getMaxParallelRequestsCount(); i++) { + try { + ListenableFuture> pendingFuture = pendingFutures.removeFirst(); + try { + pendingFuture.get(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS); + } catch (Exception e) { + log.warn("Timeout during waiting for reply!", e); + pendingFuture.cancel(true); + } + } catch (Exception e) { + log.warn("Failure during waiting for reply!", e); + } + } + } + } + + private void processHttpClientError(HttpStatus statusCode, TbMsg msg, TbRedisQueueProcessor queueProcessor) { + if (statusCode.is4xxClientError()) { + log.warn("[{}] Client error during message delivering!", msg); + } else { + queueProcessor.pushOnFailure(msg); + } + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index d69e594513..0cc3fe7aa2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -28,6 +28,8 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration headers; private boolean useSimpleClientHttpFactory; + private int readTimeoutMs; + private int maxParallelRequestsCount; private boolean useRedisQueueForMsgPersistence; private boolean trimQueue; private int maxQueueSize; @@ -39,6 +41,8 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration Date: Fri, 29 Nov 2019 17:21:10 +0200 Subject: [PATCH 088/120] JS Stats --- .../server/actors/ActorSystemContext.java | 17 +++ .../actors/ruleChain/DefaultTbContext.java | 21 ++++ .../ConsistentClusterRoutingService.java | 6 + .../service/script/RemoteJsInvokeService.java | 30 ++++- .../thingsboard/server/utils/MiscUtils.java | 1 + .../src/main/resources/thingsboard.yml | 4 + .../ConsistentClusterRoutingServiceTest.java | 118 ++++++++++++++++++ .../rule/engine/api/TbContext.java | 6 + .../rule/engine/action/TbClearAlarmNode.java | 2 + .../rule/engine/action/TbCreateAlarmNode.java | 18 ++- .../rule/engine/action/TbLogNode.java | 7 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 + .../rule/engine/filter/TbJsFilterNode.java | 11 +- .../rule/engine/filter/TbJsSwitchNode.java | 11 +- .../transform/TbAbstractTransformNode.java | 23 ++-- .../engine/transform/TbTransformMsgNode.java | 16 +++ 16 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index eecf979a70..fa82d27971 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleChainTransactionService; @@ -89,6 +90,7 @@ import java.io.StringWriter; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; @Slf4j @Component @@ -286,6 +288,21 @@ public class ActorSystemContext { @Getter private long statisticsPersistFrequency; + @Getter + private final AtomicInteger jsInvokeRequestsCount = new AtomicInteger(0); + @Getter + private final AtomicInteger jsInvokeResponsesCount = new AtomicInteger(0); + @Getter + private final AtomicInteger jsInvokeFailuresCount = new AtomicInteger(0); + + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") + public void printStats() { + if (statisticsEnabled) { + log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]", + jsInvokeRequestsCount.getAndSet(0), jsInvokeResponsesCount.getAndSet(0), jsInvokeFailuresCount.getAndSet(0)); + } + } + @Value("${actors.tenant.create_components_on_init}") @Getter private boolean tenantComponentsInitEnabled; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index ae60262817..e5e326e597 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -229,6 +229,27 @@ class DefaultTbContext implements TbContext { return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); } + @Override + public void logJsEvalRequest() { + if (mainCtx.isStatisticsEnabled()) { + mainCtx.getJsInvokeRequestsCount().incrementAndGet(); + } + } + + @Override + public void logJsEvalResponse() { + if (mainCtx.isStatisticsEnabled()) { + mainCtx.getJsInvokeResponsesCount().incrementAndGet(); + } + } + + @Override + public void logJsEvalFailure() { + if (mainCtx.isStatisticsEnabled()) { + mainCtx.getJsInvokeFailuresCount().incrementAndGet(); + } + } + @Override public String getNodeId() { return mainCtx.getNodeIdProvider().getNodeId(); diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java index 99051389c8..0bd1eb047d 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java @@ -131,15 +131,21 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService { private void addNode(ServerInstance instance) { for (int i = 0; i < virtualNodesSize; i++) { circles[instance.getServerAddress().getServerType().ordinal()].put(hash(instance, i).asLong(), instance); +// circles[instance.getServerAddress().getServerType().ordinal()].put(classic(instance, i), instance); } } private void removeNode(ServerInstance instance) { for (int i = 0; i < virtualNodesSize; i++) { circles[instance.getServerAddress().getServerType().ordinal()].remove(hash(instance, i).asLong()); +// circles[instance.getServerAddress().getServerType().ordinal()].remove(classic(instance, i)); } } + private long classic(ServerInstance instance, int i) { + return (instance.getHost() + instance.getPort() + i).hashCode() * (Long.MAX_VALUE / Integer.MAX_VALUE); + } + private HashCode hash(ServerInstance instance, int i) { return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash(); } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 4f73a59961..5eb339ae02 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; @@ -29,13 +30,14 @@ import org.thingsboard.server.kafka.TBKafkaProducerTemplate; import org.thingsboard.server.kafka.TbKafkaRequestTemplate; import org.thingsboard.server.kafka.TbKafkaSettings; import org.thingsboard.server.kafka.TbNodeIdProvider; -import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; @Slf4j @ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "remote", matchIfMissing = true) @@ -70,6 +72,25 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Value("${js.remote.max_errors}") private int maxErrors; + @Value("${js.remote.stats.enabled:false}") + private boolean statsEnabled; + + private final AtomicInteger kafkaPushedMsgs = new AtomicInteger(0); + private final AtomicInteger kafkaInvokeMsgs = new AtomicInteger(0); + private final AtomicInteger kafkaEvalMsgs = new AtomicInteger(0); + private final AtomicInteger kafkaFailedMsgs = new AtomicInteger(0); + + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") + public void printStats() { + if (statsEnabled) { + int invokeMsgs = kafkaInvokeMsgs.getAndSet(0); + int evalMsgs = kafkaEvalMsgs.getAndSet(0); + int failed = kafkaFailedMsgs.getAndSet(0); + log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}]", + kafkaPushedMsgs.getAndSet(0), invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed); + } + } + private TbKafkaRequestTemplate kafkaTemplate; private Map scriptIdToBodysMap = new ConcurrentHashMap<>(); @@ -139,14 +160,17 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.trace("Post compile request for scriptId [{}]", scriptId); ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); + kafkaPushedMsgs.incrementAndGet(); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); if (compilationResult.getSuccess()) { + kafkaEvalMsgs.incrementAndGet(); scriptIdToNameMap.put(scriptId, functionName); scriptIdToBodysMap.put(scriptId, scriptBody); return compiledScriptId; } else { + kafkaFailedMsgs.incrementAndGet(); log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); throw new RuntimeException(compilationResult.getErrorDetails()); } @@ -174,12 +198,16 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setInvokeRequest(jsRequestBuilder.build()) .build(); + ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); + kafkaPushedMsgs.incrementAndGet(); return Futures.transform(future, response -> { JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse(); if (invokeResult.getSuccess()) { + kafkaInvokeMsgs.incrementAndGet(); return invokeResult.getResult(); } else { + kafkaFailedMsgs.incrementAndGet(); log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw new RuntimeException(invokeResult.getErrorDetails()); } diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index 7a4648f234..67fbf15be5 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -19,6 +19,7 @@ import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import java.nio.charset.Charset; +import java.util.Random; /** diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8fd418a153..92977ba42f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -246,6 +246,7 @@ actors: statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" + js_print_interval_ms: "${ACTORS_JS_STATISTICS_PRINT_INTERVAL_MS:10000}" persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" queue: # Enable/disable persistence of un-processed messages to the queue @@ -467,6 +468,9 @@ js: response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" + stats: + enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}" + print_interval_ms: "${TB_JS_REMOTE_STATS_PRINT_INTERVAL_MS:10000}" transport: type: "${TRANSPORT_TYPE:local}" # local or remote diff --git a/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java b/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java new file mode 100644 index 0000000000..ff45a4737d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.cluster.routing; + +import com.datastax.driver.core.utils.UUIDs; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.cluster.ServerType; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class ConsistentClusterRoutingServiceTest { + + private ConsistentClusterRoutingService clusterRoutingService; + + private DiscoveryService discoveryService; + + private String hashFunctionName = "murmur3_128"; + private Integer virtualNodesSize = 1024*64; + private ServerAddress currentServer = new ServerAddress(" 100.96.1.0", 9001, ServerType.CORE); + + + @Before + public void setup() throws Exception { + discoveryService = mock(DiscoveryService.class); + clusterRoutingService = new ConsistentClusterRoutingService(); + ReflectionTestUtils.setField(clusterRoutingService, "discoveryService", discoveryService); + ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName); + ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize); + when(discoveryService.getCurrentServer()).thenReturn(new ServerInstance(currentServer)); + List otherServers = new ArrayList<>(); + for (int i = 1; i < 30; i++) { + otherServers.add(new ServerInstance(new ServerAddress(" 100.96." + i*2 + "." + i, 9001, ServerType.CORE))); + } + when(discoveryService.getOtherServers()).thenReturn(otherServers); + clusterRoutingService.init(); + } + + @Test + public void testDispersionOnMillionDevices() { + List devices = new ArrayList<>(); + for (int i = 0; i < 1000000; i++) { + devices.add(new DeviceId(UUIDs.timeBased())); + } + + testDevicesDispersion(devices); + } + + @Test + public void testDispersionOnDevicesFromFile() throws IOException { + List deviceIdsStrList = Files.readAllLines(Paths.get("/home/ashvayka/Downloads/deviceIds.out")); + List devices = deviceIdsStrList.stream().map(String::trim).filter(s -> !s.isEmpty()).map(UUIDConverter::fromString).map(DeviceId::new).collect(Collectors.toList()); + System.out.println("Devices: " + devices.size()); + testDevicesDispersion(devices); + testDevicesDispersion(devices); + testDevicesDispersion(devices); + testDevicesDispersion(devices); + testDevicesDispersion(devices); + + } + + private void testDevicesDispersion(List devices) { + long start = System.currentTimeMillis(); + Map map = new HashMap<>(); + for (DeviceId deviceId : devices) { + ServerAddress address = clusterRoutingService.resolveById(deviceId).orElse(currentServer); + map.put(address, map.getOrDefault(address, 0) + 1); + } + + List> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); + long end = System.currentTimeMillis(); + System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + (data.get(data.size() - 1).getValue() - data.get(0).getValue())); + + for (Map.Entry entry : data) { +// System.out.println(entry.getKey().getHost() + ": " + entry.getValue()); + } + + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index a1902d3131..d91f12da96 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -123,6 +123,12 @@ public interface TbContext { ScriptEngine createJsScriptEngine(String script, String... argNames); + void logJsEvalRequest(); + + void logJsEvalResponse(); + + void logJsEvalFailure(); + String getNodeId(); RuleChainTransactionService getRuleChainTransactionService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java index e94f236a74..0a7f6113f1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java @@ -65,8 +65,10 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { + ctx.logJsEvalRequest(); ListenableFuture asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails()); return Futures.transformAsync(asyncDetails, details -> { + ctx.logJsEvalResponse(); ListenableFuture clearFuture = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), details, System.currentTimeMillis()); return Futures.transformAsync(clearFuture, cleared -> { ListenableFuture savedAlarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index dcf7b6987d..d6f6236311 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -42,10 +42,10 @@ import java.io.IOException; nodeDescription = "Create or Update Alarm", nodeDetails = "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + - "Node output:\n" + - "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains one of those properties 'isNewAlarm/isExistingAlarm'. " + - "Message payload can be accessed via msg property. For example 'temperature = ' + msg.temperature ;. " + - "Message metadata can be accessed via metadata property. For example 'name = ' + metadata.customerName;.", + "Node output:\n" + + "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains one of those properties 'isNewAlarm/isExistingAlarm'. " + + "Message payload can be accessed via msg property. For example 'temperature = ' + msg.temperature ;. " + + "Message metadata can be accessed via metadata property. For example 'name = ' + metadata.customerName;.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeCreateAlarmConfig", icon = "notifications_active" @@ -103,11 +103,15 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) { ListenableFuture asyncAlarm; - if (msgAlarm != null ) { + if (msgAlarm != null) { asyncAlarm = Futures.immediateCheckedFuture(msgAlarm); } else { + ctx.logJsEvalRequest(); asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null), - details -> buildAlarm(msg, details, ctx.getTenantId())); + details -> { + ctx.logJsEvalResponse(); + return buildAlarm(msg, details, ctx.getTenantId()); + }); } ListenableFuture asyncCreated = Futures.transform(asyncAlarm, alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor()); @@ -115,7 +119,9 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) { + ctx.logJsEvalRequest(); ListenableFuture asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, existingAlarm.getDetails()), (Function) details -> { + ctx.logJsEvalResponse(); if (msgAlarm != null) { existingAlarm.setSeverity(msgAlarm.getSeverity()); existingAlarm.setPropagate(msgAlarm.isPropagate()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index a11a4bf18b..f3fd84788c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -53,12 +53,17 @@ public class TbLogNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { ListeningExecutor jsExecutor = ctx.getJsExecutor(); + ctx.logJsEvalRequest(); withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), toString -> { + ctx.logJsEvalResponse(); log.info(toString); ctx.tellNext(msg, SUCCESS); }, - t -> ctx.tellFailure(msg, t)); + t -> { + ctx.logJsEvalResponse(); + ctx.tellFailure(msg, t); + }); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 478b0b4666..d5ac552595 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -129,7 +129,9 @@ public class TbMsgGeneratorNode implements TbNode { prevMsg = ctx.newMsg("", originatorId, new TbMsgMetaData(), "{}"); } if (initialized) { + ctx.logJsEvalRequest(); TbMsg generated = jsEngine.executeGenerate(prevMsg); + ctx.logJsEvalResponse(); prevMsg = ctx.newMsg(generated.getType(), originatorId, generated.getMetaData(), generated.getData()); } return prevMsg; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index c23043bbb0..a9b287fa4c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -52,9 +52,16 @@ public class TbJsFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { ListeningExecutor jsExecutor = ctx.getJsExecutor(); + ctx.logJsEvalRequest(); withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(msg)), - filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), - t -> ctx.tellFailure(msg, t)); + filterResult -> { + ctx.logJsEvalResponse(); + ctx.tellNext(msg, filterResult ? "True" : "False"); + }, + t -> { + ctx.tellFailure(msg, t); + ctx.logJsEvalFailure(); + }, ctx.getDbCallbackExecutor()); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 0122b9fed7..5ba4d2b0dd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -54,9 +54,16 @@ public class TbJsSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { ListeningExecutor jsExecutor = ctx.getJsExecutor(); + ctx.logJsEvalRequest(); withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(msg)), - result -> processSwitch(ctx, msg, result), - t -> ctx.tellFailure(msg, t)); + result -> { + ctx.logJsEvalResponse(); + processSwitch(ctx, msg, result); + }, + t -> { + ctx.logJsEvalFailure(); + ctx.tellFailure(msg, t); + }, ctx.getDbCallbackExecutor()); } private void processSwitch(TbContext ctx, TbMsg msg, Set nextRelations) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java index 0e14a31a63..b1f282b742 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java @@ -44,14 +44,21 @@ public abstract class TbAbstractTransformNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { withCallback(transform(ctx, msg), - m -> { - if (m != null) { - ctx.tellNext(m, SUCCESS); - } else { - ctx.tellNext(msg, FAILURE); - } - }, - t -> ctx.tellFailure(msg, t)); + m -> transformSuccess(ctx, msg, m), + t -> transformFailure(ctx, msg, t), + ctx.getDbCallbackExecutor()); + } + + protected void transformFailure(TbContext ctx, TbMsg msg, Throwable t) { + ctx.tellFailure(msg, t); + } + + protected void transformSuccess(TbContext ctx, TbMsg msg, TbMsg m) { + if (m != null) { + ctx.tellNext(m, SUCCESS); + } else { + ctx.tellNext(msg, FAILURE); + } } protected abstract ListenableFuture transform(TbContext ctx, TbMsg msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 81c4483166..a9239ae924 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -21,6 +21,9 @@ import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; + @RuleNode( type = ComponentType.TRANSFORMATION, name = "script", @@ -49,9 +52,22 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { @Override protected ListenableFuture transform(TbContext ctx, TbMsg msg) { + ctx.logJsEvalRequest(); return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(msg)); } + @Override + protected void transformSuccess(TbContext ctx, TbMsg msg, TbMsg m) { + ctx.logJsEvalResponse(); + super.transformSuccess(ctx, msg, m); + } + + @Override + protected void transformFailure(TbContext ctx, TbMsg msg, Throwable t) { + ctx.logJsEvalFailure(); + super.transformFailure(ctx, msg, t); + } + @Override public void destroy() { if (jsEngine != null) { From da9b2b4960f61f828e7df96c1018bde99a9d9bdc Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 29 Nov 2019 17:43:39 +0200 Subject: [PATCH 089/120] Code cleanup --- .../routing/ConsistentClusterRoutingService.java | 6 ------ .../ConsistentClusterRoutingServiceTest.java | 15 +-------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java index 0bd1eb047d..99051389c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java @@ -131,21 +131,15 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService { private void addNode(ServerInstance instance) { for (int i = 0; i < virtualNodesSize; i++) { circles[instance.getServerAddress().getServerType().ordinal()].put(hash(instance, i).asLong(), instance); -// circles[instance.getServerAddress().getServerType().ordinal()].put(classic(instance, i), instance); } } private void removeNode(ServerInstance instance) { for (int i = 0; i < virtualNodesSize; i++) { circles[instance.getServerAddress().getServerType().ordinal()].remove(hash(instance, i).asLong()); -// circles[instance.getServerAddress().getServerType().ordinal()].remove(classic(instance, i)); } } - private long classic(ServerInstance instance, int i) { - return (instance.getHost() + instance.getPort() + i).hashCode() * (Long.MAX_VALUE / Integer.MAX_VALUE); - } - private HashCode hash(ServerInstance instance, int i) { return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash(); } diff --git a/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java b/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java index ff45a4737d..ceff438b41 100644 --- a/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java @@ -54,7 +54,7 @@ public class ConsistentClusterRoutingServiceTest { private DiscoveryService discoveryService; private String hashFunctionName = "murmur3_128"; - private Integer virtualNodesSize = 1024*64; + private Integer virtualNodesSize = 1024*4; private ServerAddress currentServer = new ServerAddress(" 100.96.1.0", 9001, ServerType.CORE); @@ -84,19 +84,6 @@ public class ConsistentClusterRoutingServiceTest { testDevicesDispersion(devices); } - @Test - public void testDispersionOnDevicesFromFile() throws IOException { - List deviceIdsStrList = Files.readAllLines(Paths.get("/home/ashvayka/Downloads/deviceIds.out")); - List devices = deviceIdsStrList.stream().map(String::trim).filter(s -> !s.isEmpty()).map(UUIDConverter::fromString).map(DeviceId::new).collect(Collectors.toList()); - System.out.println("Devices: " + devices.size()); - testDevicesDispersion(devices); - testDevicesDispersion(devices); - testDevicesDispersion(devices); - testDevicesDispersion(devices); - testDevicesDispersion(devices); - - } - private void testDevicesDispersion(List devices) { long start = System.currentTimeMillis(); Map map = new HashMap<>(); From 28e2c74ce3b5f3beda9bfaac9ba16cf42aea9523 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 29 Nov 2019 18:31:24 +0200 Subject: [PATCH 090/120] JS Invoke become async --- .../script/RuleNodeJsScriptEngine.java | 48 ++++++++++++++++++- .../rule/engine/api/ScriptEngine.java | 5 ++ .../rule/engine/filter/TbJsFilterNode.java | 3 +- .../engine/transform/TbTransformMsgNode.java | 2 +- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 8b996c6257..12150b806a 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -109,6 +111,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return unbindMsg(result, msg); } + @Override + public ListenableFuture executeUpdateAsync(TbMsg msg) { + ListenableFuture result = executeScriptAsync(msg); + return Futures.transformAsync(result, json -> { + if (!json.isObject()) { + log.warn("Wrong result type: {}", json.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); + } else { + return Futures.immediateFuture(unbindMsg(json, msg)); + } + }); + } + @Override public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException { JsonNode result = executeScript(prevMsg); @@ -144,6 +159,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return result.asBoolean(); } + @Override + public ListenableFuture executeFilterAsync(TbMsg msg) { + ListenableFuture result = executeScriptAsync(msg); + return Futures.transformAsync(result, json -> { + if (!json.isBoolean()) { + log.warn("Wrong result type: {}", json.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); + } else { + return Futures.immediateFuture(json.asBoolean()); + } + }); + } + @Override public Set executeSwitch(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); @@ -173,7 +201,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return mapper.readTree(eval); } catch (ExecutionException e) { if (e.getCause() instanceof ScriptException) { - throw (ScriptException)e.getCause(); + throw (ScriptException) e.getCause(); } else if (e.getCause() instanceof RuntimeException) { throw new ScriptException(e.getCause().getMessage()); } else { @@ -184,6 +212,24 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } } + private ListenableFuture executeScriptAsync(TbMsg msg) { + String[] inArgs = prepareArgs(msg); + return Futures.transformAsync(sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]), + o -> { + try { + return Futures.immediateFuture(mapper.readTree(o.toString())); + } catch (Exception e) { + if (e.getCause() instanceof ScriptException) { + return Futures.immediateFailedFuture(e.getCause()); + } else if (e.getCause() instanceof RuntimeException) { + return Futures.immediateFailedFuture(new ScriptException(e.getCause().getMessage())); + } else { + return Futures.immediateFailedFuture(new ScriptException(e)); + } + } + }); + } + public void destroy() { sandboxService.release(this.scriptId); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index 4d9a4e612b..f72adf04ab 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.msg.TbMsg; import javax.script.ScriptException; @@ -25,10 +26,14 @@ public interface ScriptEngine { TbMsg executeUpdate(TbMsg msg) throws ScriptException; + ListenableFuture executeUpdateAsync(TbMsg msg); + TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException; boolean executeFilter(TbMsg msg) throws ScriptException; + ListenableFuture executeFilterAsync(TbMsg msg); + Set executeSwitch(TbMsg msg) throws ScriptException; JsonNode executeJson(TbMsg msg) throws ScriptException; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index a9b287fa4c..2effb714b9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -51,9 +51,8 @@ public class TbJsFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ListeningExecutor jsExecutor = ctx.getJsExecutor(); ctx.logJsEvalRequest(); - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(msg)), + withCallback(jsEngine.executeFilterAsync(msg), filterResult -> { ctx.logJsEvalResponse(); ctx.tellNext(msg, filterResult ? "True" : "False"); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index a9239ae924..47e5b3c9c9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -53,7 +53,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { @Override protected ListenableFuture transform(TbContext ctx, TbMsg msg) { ctx.logJsEvalRequest(); - return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(msg)); + return jsEngine.executeUpdateAsync(msg); } @Override From 51bec36d0d608dfa87b6381e410d2cb01baa0482 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 28 Nov 2019 20:37:16 +0200 Subject: [PATCH 091/120] Fix initcallback function in google maps --- ui/src/app/widget/lib/google-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index 0f0a118f3d..a91305a1a5 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -55,7 +55,7 @@ export default class TbGoogleMap { angular.merge({imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'}, markerClusteringSetting)); } if (initCallback) { - initCallback(); + setTimeout(initCallback, 0);// eslint-disable-line } } From eafbc427d84894266998445b486cb6b9c09f10ca Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Fri, 29 Nov 2019 01:37:55 +0200 Subject: [PATCH 092/120] realize fitBounds add new property useDefaultZoom --- ui/src/app/widget/lib/google-map.js | 4 ++-- ui/src/app/widget/lib/map-widget2.js | 6 +++--- ui/src/app/widget/lib/openstreet-map.js | 4 ++-- ui/src/app/widget/lib/tencent-map.js | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index a91305a1a5..5a42f2672a 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -421,8 +421,8 @@ export default class TbGoogleMap { /* eslint-disable no-undef */ - fitBounds(bounds) { - if (this.dontFitMapBounds && this.defaultZoomLevel) { + fitBounds(bounds, useDefaultZoom) { + if ((this.dontFitMapBounds || useDefaultZoom) && this.defaultZoomLevel) { this.map.setZoom(this.defaultZoomLevel); this.map.setCenter(bounds.getCenter()); } else { diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js index 005d50afaf..4a65767d7f 100644 --- a/ui/src/app/widget/lib/map-widget2.js +++ b/ui/src/app/widget/lib/map-widget2.js @@ -732,7 +732,7 @@ export default class TbMapWidgetV2 { } }); - tbMap.map.fitBounds(bounds); + tbMap.map.fitBounds(bounds, tbMap.isEdit && tbMap.markers.length === 1); } function mapPolygonArray (rawArray) { @@ -781,7 +781,7 @@ export default class TbMapWidgetV2 { return !(!ds[tbMap.locationSettings.latKeyName] && !ds[tbMap.locationSettings.lngKeyName]); }); tbMap.initBounds = !dataValid; - tbMap.map.fitBounds(bounds); + tbMap.map.fitBounds(bounds, tbMap.isEdit && tbMap.markers.length === 1); } } @@ -845,7 +845,7 @@ export default class TbMapWidgetV2 { }) } } - map.fitBounds(bounds); + map.fitBounds(bounds, map.isEdit && map.markers.length === 1); } } } diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js index c3b54e7ed8..3d4e8b840f 100644 --- a/ui/src/app/widget/lib/openstreet-map.js +++ b/ui/src/app/widget/lib/openstreet-map.js @@ -273,9 +273,9 @@ export default class TbOpenStreetMap { polygon.redraw(); } - fitBounds(bounds) { + fitBounds(bounds, useDefaultZoom) { if (bounds.isValid()) { - if (this.dontFitMapBounds && this.defaultZoomLevel) { + if ((this.dontFitMapBounds || useDefaultZoom) && this.defaultZoomLevel) { this.map.setZoom(this.defaultZoomLevel, {animate: false}); this.map.panTo(bounds.getCenter(), {animate: false}); } else { diff --git a/ui/src/app/widget/lib/tencent-map.js b/ui/src/app/widget/lib/tencent-map.js index 0352822f4f..9fa390cdc9 100644 --- a/ui/src/app/widget/lib/tencent-map.js +++ b/ui/src/app/widget/lib/tencent-map.js @@ -58,7 +58,7 @@ export default class TbTencentMap { if (initCallback) { - initCallback(); + setTimeout(initCallback, 0);// eslint-disable-line } } @@ -427,8 +427,8 @@ export default class TbTencentMap { } /* eslint-disable no-undef ,no-unused-vars*/ - fitBounds(bounds) { - if (this.dontFitMapBounds && this.defaultZoomLevel) { + fitBounds(bounds, useDefaultZoom) { + if ((this.dontFitMapBounds || useDefaultZoom) && this.defaultZoomLevel) { this.map.setZoom(this.defaultZoomLevel); this.map.setCenter(bounds.getCenter()); } else { From 8569b7dac248a0b27668ee01150c1ee68d3adc73 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 2 Dec 2019 16:09:02 +0200 Subject: [PATCH 093/120] Change bound from edit mode map --- ui/src/app/widget/lib/map-widget2.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js index 4a65767d7f..c8d2428ded 100644 --- a/ui/src/app/widget/lib/map-widget2.js +++ b/ui/src/app/widget/lib/map-widget2.js @@ -781,7 +781,10 @@ export default class TbMapWidgetV2 { return !(!ds[tbMap.locationSettings.latKeyName] && !ds[tbMap.locationSettings.lngKeyName]); }); tbMap.initBounds = !dataValid; - tbMap.map.fitBounds(bounds, tbMap.isEdit && tbMap.markers.length === 1); + + if(!tbMap.isEdit && tbMap.markers.length !== 1 || tbMap.polylines || tbMap.polygons) { + tbMap.map.fitBounds(bounds); + } } } @@ -845,7 +848,11 @@ export default class TbMapWidgetV2 { }) } } - map.fitBounds(bounds, map.isEdit && map.markers.length === 1); + if((!map.isEdit && map.markers && map.markers.length !== 1) || + (this.polylines && this.polylines.length > 0) || + (this.polygons && this.polygons.length > 0)) { + map.fitBounds(bounds); + } } } } From 71744d4808395aea95a33627df7d2cb272537645 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Tue, 3 Dec 2019 12:15:14 +0200 Subject: [PATCH 094/120] Feature/rest client (#2229) * added methods from admin-controller, alarm-controller, asset-controller, audit-log-controller * refactored rest client and added methods from auth controller * added methods from component-descriptor-controller * added methods from customer controller * added methods from dashboard controller * added methods from device controller * refactored url pageLink params * added methods from entity relation controller * added methods from entity view controller * refactored * added methods from event controller * added methods from rpc controller * added methods from rule chain controller * added methods from telemetry controller * added methods from tenant controller * added methods from user controller * added methods from widgets bundle controller * added methods from widget type controller * created method refreshToken * moved classes SecuritySettings, UserPasswordPolicy, ClaimRequest, UpdateMessage, to common module, and added "/api" to urls where this part was missing * refactored --- .../thingsboard/client/tools/RestClient.java | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 248a93fef1..5296ea8129 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -16,6 +16,8 @@ package org.thingsboard.client.tools; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; @@ -27,6 +29,7 @@ import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; +import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.async.DeferredResult; @@ -90,9 +93,11 @@ public class RestClient implements ClientHttpRequestInterceptor { protected final String baseURL; private String token; private String refreshToken; + private final ObjectMapper objectMapper = new ObjectMapper(); private final static String TIME_PAGE_LINK_URL_PARAMS = "limit={limit}&startTime={startTime}&endTime={endTime}&ascOrder={ascOrder}&offset={offset}"; private final static String TEXT_PAGE_LINK_URL_PARAMS = "limit={limit}&textSearch{textSearch}&idOffset={idOffset}&textOffset{textOffset}"; + protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; @Override public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { @@ -110,6 +115,14 @@ public class RestClient implements ClientHttpRequestInterceptor { return response; } + public String getToken() { + return token; + } + + public String getRefreshToken() { + return refreshToken; + } + public void refreshToken() { Map refreshTokenRequest = new HashMap<>(); refreshTokenRequest.put("refreshToken", refreshToken); @@ -297,10 +310,6 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate; } - public String getToken() { - return token; - } - public Optional getAdminSettings(String key) { try { ResponseEntity adminSettings = restTemplate.getForEntity(baseURL + "/api/admin/settings/{key}", AdminSettings.class, key); @@ -629,6 +638,11 @@ public class RestClient implements ClientHttpRequestInterceptor { return auditLog.getBody(); } + public String getActivateToken(String userId) { + String activationLink = getActivationLink(userId); + return StringUtils.delete(activationLink, baseURL + ACTIVATE_TOKEN_REGEX); + } + public Optional getUser() { ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/auth/user", User.class); return Optional.ofNullable(user.getBody()); @@ -638,7 +652,10 @@ public class RestClient implements ClientHttpRequestInterceptor { restTemplate.exchange(URI.create(baseURL + "/api/auth/logout"), HttpMethod.POST, HttpEntity.EMPTY, Object.class); } - public void changePassword(JsonNode changePasswordRequest) { + public void changePassword(String currentPassword, String newPassword) { + ObjectNode changePasswordRequest = objectMapper.createObjectNode(); + changePasswordRequest.put("currentPassword", currentPassword); + changePasswordRequest.put("newPassword", newPassword); restTemplate.exchange(URI.create(baseURL + "/api/auth/changePassword"), HttpMethod.POST, new HttpEntity<>(changePasswordRequest), Object.class); } @@ -655,12 +672,13 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public ResponseEntity checkActivateToken(String activateToken) { return restTemplate.getForEntity(baseURL + "/api/noauth/activate?activateToken={activateToken}", String.class, activateToken); } - public void requestResetPasswordByEmail(JsonNode resetPasswordByEmailRequest) { + public void requestResetPasswordByEmail(String email) { + ObjectNode resetPasswordByEmailRequest = objectMapper.createObjectNode(); + resetPasswordByEmailRequest.put("email", email); restTemplate.exchange(URI.create(baseURL + "/api/noauth/resetPasswordByEmail"), HttpMethod.POST, new HttpEntity<>(resetPasswordByEmailRequest), Object.class); } @@ -668,7 +686,10 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate.getForEntity(baseURL + "/api/noauth/resetPassword?resetToken={resetToken}", String.class, resetToken); } - public Optional activateUser(JsonNode activateRequest) { + public Optional activateUser(String userId, String password) { + ObjectNode activateRequest = objectMapper.createObjectNode(); + activateRequest.put("activateToken", getActivateToken(userId)); + activateRequest.put("password", password); try { ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); return Optional.ofNullable(jsonNode.getBody()); @@ -681,7 +702,10 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Optional resetPassword(JsonNode resetPasswordRequest) { + public Optional resetPassword(String resetToken, String resetPassword) { + ObjectNode resetPasswordRequest = objectMapper.createObjectNode(); + resetPasswordRequest.put("resetToken", resetToken); + resetPasswordRequest.put("resetPassword", resetPassword); try { ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/resetPassword", resetPasswordRequest, JsonNode.class); return Optional.ofNullable(jsonNode.getBody()); @@ -696,7 +720,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public Optional getComponentDescriptorByClazz(String componentDescriptorClazz) { try { - ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/api/component/{componentDescriptorClazz}", ComponentDescriptor.class); + ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/api/component/{componentDescriptorClazz}", ComponentDescriptor.class, componentDescriptorClazz); return Optional.ofNullable(componentDescriptor.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1807,31 +1831,21 @@ public class RestClient implements ClientHttpRequestInterceptor { restTemplate.postForEntity(baseURL + "/api/user/sendActivationMail?email={email}", null, Object.class, email); } - public Optional getActivationLink(String userId) { - try { - ResponseEntity activationLink = restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId); - return Optional.ofNullable(activationLink.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } + public String getActivationLink(String userId) { + return restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId).getBody(); } public void deleteUser(String userId) { restTemplate.delete(baseURL + "/api/user/{userId}", userId); } - // @RequestMapping(value = "/tenant/{tenantId}/users", params = {"limit"}, method = RequestMethod.GET) public TextPageData getTenantAdmins(String tenantId, TextPageLink pageLink) { Map params = new HashMap<>(); params.put("tenantId", tenantId); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenant/{tenantId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/{tenantId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { From 5fe40b70b3fc85648f25cec22cb259e47265a4ba Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 3 Dec 2019 12:38:52 +0200 Subject: [PATCH 095/120] No more protobuf in JS executor --- .../service/script/RemoteJsInvokeService.java | 16 ------------- .../script/RemoteJsRequestEncoder.java | 10 +++++++- .../script/RemoteJsResponseDecoder.java | 6 ++++- application/src/main/proto/jsinvoke.proto | 9 +++---- .../server/kafka/TBKafkaProducerTemplate.java | 14 +---------- .../server/kafka/TbKafkaEnricher.java | 24 ------------------- .../server/kafka/TbKafkaRequestTemplate.java | 11 ++++----- .../server/kafka/TbKafkaSettings.java | 5 ++-- 8 files changed, 25 insertions(+), 70 deletions(-) delete mode 100644 common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaEnricher.java diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 5eb339ae02..4ba4efb4b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -101,22 +101,6 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { requestBuilder.clientId("producer-js-invoke-" + nodeIdProvider.getNodeId()); requestBuilder.defaultTopic(requestTopic); requestBuilder.encoder(new RemoteJsRequestEncoder()); - requestBuilder.enricher((request, responseTopic, requestId) -> { - JsInvokeProtos.RemoteJsRequest.Builder remoteRequest = JsInvokeProtos.RemoteJsRequest.newBuilder(); - if (request.hasCompileRequest()) { - remoteRequest.setCompileRequest(request.getCompileRequest()); - } - if (request.hasInvokeRequest()) { - remoteRequest.setInvokeRequest(request.getInvokeRequest()); - } - if (request.hasReleaseRequest()) { - remoteRequest.setReleaseRequest(request.getReleaseRequest()); - } - remoteRequest.setResponseTopic(responseTopic); - remoteRequest.setRequestIdMSB(requestId.getMostSignificantBits()); - remoteRequest.setRequestIdLSB(requestId.getLeastSignificantBits()); - return remoteRequest.build(); - }); TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder responseBuilder = TBKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java index db1a75add5..d42d221d24 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java @@ -15,15 +15,23 @@ */ package org.thingsboard.server.service.script; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.kafka.TbKafkaEncoder; +import java.nio.charset.StandardCharsets; + /** * Created by ashvayka on 25.09.18. */ public class RemoteJsRequestEncoder implements TbKafkaEncoder { @Override public byte[] encode(JsInvokeProtos.RemoteJsRequest value) { - return value.toByteArray(); + try { + return JsonFormat.printer().print(value).getBytes(StandardCharsets.UTF_8); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java index 0ac7e6f647..8407ceaaa1 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java @@ -15,10 +15,12 @@ */ package org.thingsboard.server.service.script; +import com.google.protobuf.util.JsonFormat; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.kafka.TbKafkaDecoder; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * Created by ashvayka on 25.09.18. @@ -27,6 +29,8 @@ public class RemoteJsResponseDecoder implements TbKafkaDecoder { private final KafkaProducer producer; private final TbKafkaEncoder encoder; - @Builder.Default - private TbKafkaEnricher enricher = ((value, responseTopic, requestId) -> value); - private final TbKafkaPartitioner partitioner; private ConcurrentMap> partitionInfoMap; @Getter @@ -61,7 +58,7 @@ public class TBKafkaProducerTemplate { private final TbKafkaSettings settings; @Builder - private TBKafkaProducerTemplate(TbKafkaSettings settings, TbKafkaEncoder encoder, TbKafkaEnricher enricher, + private TBKafkaProducerTemplate(TbKafkaSettings settings, TbKafkaEncoder encoder, TbKafkaPartitioner partitioner, String defaultTopic, String clientId) { Properties props = settings.toProps(); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); @@ -72,7 +69,6 @@ public class TBKafkaProducerTemplate { this.settings = settings; this.producer = new KafkaProducer<>(props); this.encoder = encoder; - this.enricher = enricher; this.partitioner = partitioner; this.defaultTopic = defaultTopic; } @@ -93,14 +89,6 @@ public class TBKafkaProducerTemplate { } } - T enrich(T value, String responseTopic, UUID requestId) { - if (enricher != null) { - return enricher.enrich(value, responseTopic, requestId); - } else { - return value; - } - } - public Future send(String key, T value, Callback callback) { return send(key, value, null, callback); } diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaEnricher.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaEnricher.java deleted file mode 100644 index b9226bbd38..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaEnricher.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.kafka; - -import java.util.UUID; - -public interface TbKafkaEnricher { - - T enrich(T value, String responseTopic, UUID requestId); - -} diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java index 28c1e2cba2..461866e406 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java @@ -144,11 +144,11 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe tickSize = pendingRequests.size(); if (nextCleanupMs < tickTs) { //cleanup; - pendingRequests.entrySet().forEach(kv -> { - if (kv.getValue().expTime < tickTs) { - ResponseMetaData staleRequest = pendingRequests.remove(kv.getKey()); + pendingRequests.forEach((key, value) -> { + if (value.expTime < tickTs) { + ResponseMetaData staleRequest = pendingRequests.remove(key); if (staleRequest != null) { - log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", kv.getKey(), staleRequest.expTime, tickTs); + log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); staleRequest.future.setException(new TimeoutException()); } } @@ -189,13 +189,12 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe SettableFuture future = SettableFuture.create(); ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); pendingRequests.putIfAbsent(requestId, responseMetaData); - request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, key, responseMetaData.expTime); requestTemplate.send(key, request, headers, (metadata, exception) -> { if (exception != null) { log.trace("[{}] Failed to post the request", requestId, exception); } else { - log.trace("[{}] Posted the request", requestId, metadata); + log.trace("[{}] Posted the request: {}", requestId, metadata); } }); return future; diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaSettings.java index 24d53a846e..bef8244534 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaSettings.java @@ -32,9 +32,8 @@ import java.util.Properties; @Component public class TbKafkaSettings { - public static final String REQUEST_ID_HEADER = "requestId"; - public static final String RESPONSE_TOPIC_HEADER = "responseTopic"; - + static final String REQUEST_ID_HEADER = "requestId"; + static final String RESPONSE_TOPIC_HEADER = "responseTopic"; @Value("${kafka.bootstrap.servers}") private String servers; From 562917649c8e64bfff7df0328005bb5087d88dae Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2019 14:45:59 +0200 Subject: [PATCH 096/120] Improve JS Executor --- .../src/main/resources/thingsboard.yml | 2 +- msa/js-executor/api/jsExecutor.js | 50 +- .../api/jsInvokeMessageProcessor.js | 95 +-- msa/js-executor/api/utils.js | 7 +- .../config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 1 + msa/js-executor/config/logger.js | 46 +- msa/js-executor/package-lock.json | 618 +----------------- msa/js-executor/package.json | 10 +- msa/js-executor/server.js | 102 ++- 10 files changed, 238 insertions(+), 694 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 92977ba42f..16de3fd708 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -203,7 +203,7 @@ sql: attributes: batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" - stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:1000}" + stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" diff --git a/msa/js-executor/api/jsExecutor.js b/msa/js-executor/api/jsExecutor.js index 3568a6c410..7a4ffb04f1 100644 --- a/msa/js-executor/api/jsExecutor.js +++ b/msa/js-executor/api/jsExecutor.js @@ -17,11 +17,28 @@ const vm = require('vm'); -function JsExecutor() { +function JsExecutor(useSandbox) { + this.useSandbox = useSandbox; } JsExecutor.prototype.compileScript = function(code) { - return new Promise(function(resolve, reject) { + if (this.useSandbox) { + return createScript(code); + } else { + return createFunction(code); + } +} + +JsExecutor.prototype.executeScript = function(script, args, timeout) { + if (this.useSandbox) { + return invokeScript(script, args, timeout); + } else { + return invokeFunction(script, args); + } +} + +function createScript(code) { + return new Promise((resolve, reject) => { try { code = "("+code+")(...args)"; var script = new vm.Script(code); @@ -32,8 +49,8 @@ JsExecutor.prototype.compileScript = function(code) { }); } -JsExecutor.prototype.executeScript = function(script, args, timeout) { - return new Promise(function(resolve, reject) { +function invokeScript(script, args, timeout) { + return new Promise((resolve, reject) => { try { var sandbox = Object.create(null); sandbox.args = args; @@ -45,4 +62,29 @@ JsExecutor.prototype.executeScript = function(script, args, timeout) { }); } + +function createFunction(code) { + return new Promise((resolve, reject) => { + try { + code = "return ("+code+")(...args)"; + const parsingContext = vm.createContext({}); + const func = vm.compileFunction(code, ['args'], {parsingContext: parsingContext}); + resolve(func); + } catch (err) { + reject(err); + } + }); +} + +function invokeFunction(func, args) { + return new Promise((resolve, reject) => { + try { + var result = func(args); + resolve(result); + } catch (err) { + reject(err); + } + }); +} + module.exports = JsExecutor; diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index 1a1df75e09..6afc02e89c 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -15,18 +15,22 @@ */ 'use strict'; +const COMPILATION_ERROR = 0; +const RUNTIME_ERROR = 1; +const TIMEOUT_ERROR = 2; +const UNRECOGNIZED = -1; + const config = require('config'), - logger = require('../config/logger')('JsInvokeMessageProcessor'), + logger = require('../config/logger')._logger('JsInvokeMessageProcessor'), Utils = require('./utils'), - js = require('./jsinvoke.proto').js, - KeyedMessage = require('kafka-node').KeyedMessage, JsExecutor = require('./jsExecutor'); const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency')); +const useSandbox = config.get('script.use_sandbox') === 'true'; function JsInvokeMessageProcessor(producer) { this.producer = producer; - this.executor = new JsExecutor(); + this.executor = new JsExecutor(useSandbox); this.scriptMap = {}; this.executedScriptsCounter = 0; } @@ -34,18 +38,22 @@ function JsInvokeMessageProcessor(producer) { JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { var requestId; + var responseTopic; try { - var request = js.RemoteJsRequest.decode(message.value); - requestId = getRequestId(request); + var request = JSON.parse(message.value.toString('utf8')); + var buf = message.headers['requestId']; + requestId = Utils.UUIDFromBuffer(buf); + buf = message.headers['responseTopic']; + responseTopic = buf.toString('utf8'); - logger.debug('[%s] Received request, responseTopic: [%s]', requestId, request.responseTopic); + logger.debug('[%s] Received request, responseTopic: [%s]', requestId, responseTopic); if (request.compileRequest) { - this.processCompileRequest(requestId, request.responseTopic, request.compileRequest); + this.processCompileRequest(requestId, responseTopic, request.compileRequest); } else if (request.invokeRequest) { - this.processInvokeRequest(requestId, request.responseTopic, request.invokeRequest); + this.processInvokeRequest(requestId, responseTopic, request.invokeRequest); } else if (request.releaseRequest) { - this.processReleaseRequest(requestId, request.responseTopic, request.releaseRequest); + this.processReleaseRequest(requestId, responseTopic, request.releaseRequest); } else { logger.error('[%s] Unknown request recevied!', requestId); } @@ -68,7 +76,7 @@ JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, r this.sendResponse(requestId, responseTopic, scriptId, compileResponse); }, (err) => { - var compileResponse = createCompileResponse(scriptId, false, js.JsInvokeErrorCode.COMPILATION_ERROR, err); + var compileResponse = createCompileResponse(scriptId, false, COMPILATION_ERROR, err); logger.debug('[%s] Sending failed compile response, scriptId: [%s]', requestId, scriptId); this.sendResponse(requestId, responseTopic, scriptId, compileResponse); } @@ -96,9 +104,9 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, re (err) => { var errorCode; if (err.message.includes('Script execution timed out')) { - errorCode = js.JsInvokeErrorCode.TIMEOUT_ERROR; + errorCode = TIMEOUT_ERROR; } else { - errorCode = js.JsInvokeErrorCode.RUNTIME_ERROR; + errorCode = RUNTIME_ERROR; } var invokeResponse = createInvokeResponse("", false, errorCode, err); logger.debug('[%s] Sending failed invoke response, scriptId: [%s], errorCode: [%s]', requestId, scriptId, errorCode); @@ -107,8 +115,8 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, re ) }, (err) => { - var invokeResponse = createInvokeResponse("", false, js.JsInvokeErrorCode.COMPILATION_ERROR, err); - logger.debug('[%s] Sending failed invoke response, scriptId: [%s], errorCode: [%s]', requestId, scriptId, js.JsInvokeErrorCode.COMPILATION_ERROR); + var invokeResponse = createInvokeResponse("", false, COMPILATION_ERROR, err); + logger.debug('[%s] Sending failed invoke response, scriptId: [%s], errorCode: [%s]', requestId, scriptId, COMPILATION_ERROR); this.sendResponse(requestId, responseTopic, scriptId, null, invokeResponse); } ); @@ -127,15 +135,26 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, r JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseTopic, scriptId, compileResponse, invokeResponse, releaseResponse) { var remoteResponse = createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse); - var rawResponse = js.RemoteJsResponse.encode(remoteResponse).finish(); - const message = new KeyedMessage(scriptId, rawResponse); - const payloads = [ { topic: responseTopic, messages: message, key: scriptId } ]; - this.producer.send(payloads, function (err, data) { - if (err) { - logger.error('[%s] Failed to send response to kafka: %s', requestId, err.message); - logger.error(err.stack); + var rawResponse = Buffer.from(JSON.stringify(remoteResponse), 'utf8'); + this.producer.send( + { + topic: responseTopic, + messages: [ + { + key: scriptId, + value: rawResponse + } + ] } - }); + ).then( + () => {}, + (err) => { + if (err) { + logger.error('[%s] Failed to send response to kafka: %s', requestId, err.message); + logger.error(err.stack); + } + } + ); } JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scriptBody) { @@ -159,50 +178,42 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) { const requestIdBits = Utils.UUIDToBits(requestId); - return js.RemoteJsResponse.create( - { + return { requestIdMSB: requestIdBits[0], requestIdLSB: requestIdBits[1], compileResponse: compileResponse, invokeResponse: invokeResponse, releaseResponse: releaseResponse - } - ); + }; } function createCompileResponse(scriptId, success, errorCode, err) { const scriptIdBits = Utils.UUIDToBits(scriptId); - return js.JsCompileResponse.create( - { + return { errorCode: errorCode, success: success, errorDetails: parseJsErrorDetails(err), scriptIdMSB: scriptIdBits[0], scriptIdLSB: scriptIdBits[1] - } - ); + }; } function createInvokeResponse(result, success, errorCode, err) { - return js.JsInvokeResponse.create( - { + return { errorCode: errorCode, success: success, errorDetails: parseJsErrorDetails(err), result: result - } - ); + }; } function createReleaseResponse(scriptId, success) { const scriptIdBits = Utils.UUIDToBits(scriptId); - return js.JsReleaseResponse.create( - { + return { success: success, scriptIdMSB: scriptIdBits[0], scriptIdLSB: scriptIdBits[1] - } - ); + }; } function parseJsErrorDetails(err) { @@ -229,8 +240,4 @@ function getScriptId(request) { return Utils.toUUIDString(request.scriptIdMSB, request.scriptIdLSB); } -function getRequestId(request) { - return Utils.toUUIDString(request.requestIdMSB, request.requestIdLSB); -} - -module.exports = JsInvokeMessageProcessor; \ No newline at end of file +module.exports = JsInvokeMessageProcessor; diff --git a/msa/js-executor/api/utils.js b/msa/js-executor/api/utils.js index 40ab74e721..2e0381086b 100644 --- a/msa/js-executor/api/utils.js +++ b/msa/js-executor/api/utils.js @@ -18,16 +18,17 @@ const Long = require('long'), uuidParse = require('uuid-parse'); -var logger = require('../config/logger')('Utils'); - exports.toUUIDString = function(mostSigBits, leastSigBits) { var msbBytes = Long.fromValue(mostSigBits, false).toBytes(false); var lsbBytes = Long.fromValue(leastSigBits, false).toBytes(false); var uuidBytes = msbBytes.concat(lsbBytes); - var buff = new Buffer(uuidBytes, 'utf8'); return uuidParse.unparse(uuidBytes); } +exports.UUIDFromBuffer = function(buf) { + return uuidParse.unparse(buf); +} + exports.UUIDToBits = function(uuidString) { const bytes = uuidParse.parse(uuidString); var msb = Long.fromBytes(bytes.slice(0,8), false, false).toString(); diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 99f47cb5f1..c3caf15a9a 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -25,4 +25,5 @@ logger: filename: "LOGGER_FILENAME" script: + use_sandbox: "SCRIPT_USE_SANDBOX" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index fb470567e7..9688722109 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -26,4 +26,5 @@ logger: filename: "tb-js-executor-%DATE%.log" script: + use_sandbox: "true" script_body_trace_frequency: "1000" diff --git a/msa/js-executor/config/logger.js b/msa/js-executor/config/logger.js index 4f5340c2a8..317dc0cdd5 100644 --- a/msa/js-executor/config/logger.js +++ b/msa/js-executor/config/logger.js @@ -17,9 +17,24 @@ var config = require('config'), path = require('path'), DailyRotateFile = require('winston-daily-rotate-file'); +const { logLevel } = require('kafkajs'); const { createLogger, format, transports } = require('winston'); const { combine, timestamp, label, printf, splat } = format; +const toWinstonLogLevel = level => { + switch(level) { + case logLevel.ERROR: + case logLevel.NOTHING: + return 'error' + case logLevel.WARN: + return 'warn' + case logLevel.INFO: + return 'info' + case logLevel.DEBUG: + return 'debug' + } +} + var loggerTransports = []; if (process.env.NODE_ENV !== 'production' || process.env.DOCKER_MODE === 'true') { @@ -56,4 +71,33 @@ function _logger(moduleLabel) { }); } -module.exports = _logger; \ No newline at end of file +const KafkaJsWinstonLogCreator = logLevel => { + const logger = createLogger({ + level: toWinstonLogLevel(logLevel), + format:combine( + splat(), + label({ label: 'kafkajs' }), + timestamp({format: 'YYYY-MM-DD HH:mm:ss,SSS'}), + printf(info => { + var res = `${info.timestamp} [${info.label}] ${info.level.toUpperCase()}: ${info.message}`; + if (info.extra) { + res +=`: ${JSON.stringify(info.extra)}`; + } + return res; + } + ) + ), + transports: loggerTransports + }); + + return ({ namespace, level, label, log }) => { + const { message, ...extra } = log; + logger.log({ + level: toWinstonLogLevel(level), + message, + extra, + }); + } +} + +module.exports = {_logger, KafkaJsWinstonLogCreator}; diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index 52ea28f0c7..96c8341882 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -35,60 +35,6 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -106,11 +52,6 @@ "@types/node": "*" } }, - "@types/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" - }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -120,7 +61,8 @@ "@types/node": { "version": "10.12.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.10.tgz", - "integrity": "sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w==" + "integrity": "sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w==", + "dev": true }, "abbrev": { "version": "1.1.1", @@ -182,11 +124,6 @@ } } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -206,22 +143,6 @@ "normalize-path": "^2.1.1" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -331,7 +252,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -397,39 +319,12 @@ "tweetnacl": "^0.14.3" } }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", - "requires": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - } - }, "binary-extensions": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -482,6 +377,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -516,53 +412,6 @@ } } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "optional": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "optional": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "optional": true - }, - "buffermaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/buffermaker/-/buffermaker-1.2.1.tgz", - "integrity": "sha512-IdnyU2jDHU65U63JuVQNTHiWjPRH0CS3aYd/WPaEwyX84rFdukhOduAVb1jwUScmb5X0JWPw8NZOrhoLMiyAHQ==", - "requires": { - "long": "1.1.2" - }, - "dependencies": { - "long": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/long/-/long-1.1.2.tgz", - "integrity": "sha1-6u9ZUcp1UdlpJrgtokLbnWso+1M=" - } - } - }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" - }, "byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -610,14 +459,6 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", - "requires": { - "traverse": ">=0.3.0 <0.4" - } - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -650,12 +491,6 @@ "upath": "^1.0.5" } }, - "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", - "optional": true - }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -691,11 +526,6 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -774,7 +604,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "config": { "version": "3.2.2", @@ -798,11 +629,6 @@ "xdg-basedir": "^3.0.0" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -858,6 +684,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -868,19 +695,11 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true }, "deep-is": { "version": "0.1.3", @@ -935,23 +754,6 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true - }, - "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true - }, "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", @@ -1004,14 +806,6 @@ "env-variable": "0.0.x" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" - } - }, "env-variable": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", @@ -1272,12 +1066,6 @@ "moment": "^2.11.2" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1343,12 +1131,6 @@ "readable-stream": "^2.0.0" } }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "optional": true - }, "fs-extra": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", @@ -1895,22 +1677,6 @@ } } }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, "get-stream": { "version": "3.0.0", "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -1932,12 +1698,6 @@ "assert-plus": "^1.0.0" } }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", - "optional": true - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -2059,12 +1819,6 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "optional": true - }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -2150,7 +1904,8 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true }, "into-stream": { "version": "5.1.0", @@ -2262,14 +2017,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-glob": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", @@ -2453,42 +2200,12 @@ "verror": "1.10.0" } }, - "kafka-node": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/kafka-node/-/kafka-node-4.1.3.tgz", - "integrity": "sha512-C2WHksRCr7vIKmbxYaCk2c5Q1lnHIi6C0f3AioK3ARcRHGO9DpqErcoaS9d8PP62yzTnkYras+iAlmPsZHNSfw==", - "requires": { - "async": "^2.6.2", - "binary": "~0.3.0", - "bl": "^2.2.0", - "buffer-crc32": "~0.2.5", - "buffermaker": "~1.2.0", - "debug": "^2.1.3", - "denque": "^1.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "nested-error-stacks": "^2.0.0", - "optional": "^0.1.3", - "retry": "^0.10.1", - "snappy": "^6.0.1", - "uuid": "^3.0.0" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - } - } - } + "kafkajs": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-1.11.0.tgz", + "integrity": "sha512-dLRCcFIBygZucR+e8U2ZqH2wgMrAu114K0szUyUseJoeOii3cG5bHZPIdqKecXxI6begPVCfGS3R0nJY4zHW2A==", + "requires": { + "long": "^4.0.0" } }, "kind-of": { @@ -2641,16 +2358,11 @@ "mime-db": "1.40.0" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "optional": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2685,6 +2397,7 @@ "version": "0.5.1", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -2692,7 +2405,8 @@ "minimist": { "version": "0.0.8", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, @@ -2704,7 +2418,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multistream": { "version": "2.1.1", @@ -2742,26 +2457,6 @@ "to-regex": "^3.0.1" } }, - "napi-build-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", - "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==", - "optional": true - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" - }, - "node-abi": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz", - "integrity": "sha512-jmEOvv0eanWjhX8dX1pmjb7oJl1U1oR4FOh0b2GnvALwSYoOdU7sj+kLDSAyjo4pfC9aj/IxkloxdLJQhSSQBA==", - "optional": true, - "requires": { - "semver": "^5.4.1" - } - }, "nodemon": { "version": "1.18.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.7.tgz", @@ -2797,12 +2492,6 @@ } } }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", - "optional": true - }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -2830,35 +2519,12 @@ "path-key": "^2.0.0" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "optional": true - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -2917,6 +2583,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -2926,11 +2593,6 @@ "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" }, - "optional": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", - "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==" - }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -2945,12 +2607,6 @@ "wordwrap": "~1.0.0" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "optional": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -3127,38 +2783,6 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, - "prebuild-install": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", - "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", - "optional": true, - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", - "rc": "^1.2.7", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "optional": true - } - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -3182,26 +2806,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "protobufjs": { - "version": "6.8.8", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3220,16 +2824,6 @@ "integrity": "sha512-vL6NLxNHzkNTjGJUpMm5PLC+94/0tTlC1vkP9bdU0pOHih+EujMjgMTwfZopZvHWRFbqJ5Y73OMoau50PewDDA==", "dev": true }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3246,6 +2840,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -3389,11 +2984,6 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3428,12 +3018,6 @@ "semver": "^5.0.3" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "optional": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -3475,24 +3059,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", - "optional": true - }, - "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", - "optional": true, - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "simple-swizzle": { "version": "0.2.2", @@ -3615,25 +3183,6 @@ } } }, - "snappy": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/snappy/-/snappy-6.2.3.tgz", - "integrity": "sha512-HZpVoIxMfQ4fL3iDuMdI1R5xycw1o9YDCAndTKZCY/EHRoKFvzwplttuBBVGeEg2fd1hYiwAXos/sM24W7N1LA==", - "optional": true, - "requires": { - "bindings": "^1.3.1", - "nan": "^2.14.0", - "prebuild-install": "^5.2.2" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true - } - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3725,16 +3274,6 @@ "readable-stream": "^2.1.4" } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -3743,14 +3282,6 @@ "safe-buffer": "~5.1.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -3760,7 +3291,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "supports-color": { "version": "5.5.0", @@ -3771,57 +3303,6 @@ "has-flag": "^3.0.0" } }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "optional": true, - "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "optional": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "optional": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - } - } - }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -3848,12 +3329,6 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "optional": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -3923,11 +3398,6 @@ } } }, - "traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" - }, "triple-beam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", @@ -3937,6 +3407,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -4140,7 +3611,8 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true }, "uuid-parse": { "version": "1.0.0", @@ -4167,21 +3639,6 @@ "isexe": "^2.0.0" } }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -4281,7 +3738,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "2.3.0", @@ -4300,12 +3758,6 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "optional": true - }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index dbbfe6aea1..da4eb09b09 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -6,18 +6,16 @@ "main": "server.js", "bin": "server.js", "scripts": { - "build-proto": "pbjs -t static-module -w commonjs -o ./api/jsinvoke.proto.js ../../application/src/main/proto/jsinvoke.proto", - "install": "npm run build-proto && pkg -t node10-linux-x64,node10-win-x64 --out-path ./target . && node install.js", + "install": "pkg -t node10-linux-x64,node10-win-x64 --out-path ./target . && node install.js", "test": "echo \"Error: no test specified\" && exit 1", - "start": "npm run build-proto && nodemon server.js", - "start-prod": "npm run build-proto && NODE_ENV=production nodemon server.js" + "start": "nodemon server.js", + "start-prod": "NODE_ENV=production nodemon server.js" }, "dependencies": { "config": "^3.2.2", "js-yaml": "^3.12.0", - "kafka-node": "^4.1.3", + "kafkajs": "^1.11.0", "long": "^4.0.0", - "protobufjs": "^6.8.8", "uuid-parse": "^1.0.0", "winston": "^3.0.0", "winston-daily-rotate-file": "^3.2.1" diff --git a/msa/js-executor/server.js b/msa/js-executor/server.js index 8a0912bcc2..6f322f0892 100644 --- a/msa/js-executor/server.js +++ b/msa/js-executor/server.js @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +const { logLevel, Kafka } = require('kafkajs'); + const config = require('config'), - kafka = require('kafka-node'), - ConsumerGroup = kafka.ConsumerGroup, - Producer = kafka.Producer, JsInvokeMessageProcessor = require('./api/jsInvokeMessageProcessor'), - logger = require('./config/logger')('main'); + logger = require('./config/logger')._logger('main'), + KafkaJsWinstonLogCreator = require('./config/logger').KafkaJsWinstonLogCreator; var kafkaClient; +var consumer; +var producer; (async() => { try { @@ -32,49 +35,24 @@ var kafkaClient; logger.info('Kafka Bootstrap Servers: %s', kafkaBootstrapServers); logger.info('Kafka Requests Topic: %s', kafkaRequestTopic); - kafkaClient = new kafka.KafkaClient({kafkaHost: kafkaBootstrapServers}); - - var consumer = new ConsumerGroup( - { - kafkaHost: kafkaBootstrapServers, - groupId: 'js-executor-group', - autoCommit: true, - encoding: 'buffer' - }, - kafkaRequestTopic - ); - - consumer.on('error', (err) => { - logger.error('Unexpected kafka consumer error: %s', err.message); - logger.error(err.stack); - }); - - consumer.on('offsetOutOfRange', (err) => { - logger.error('Offset out of range error: %s', err.message); - logger.error(err.stack); - }); - - consumer.on('rebalancing', () => { - logger.info('Rebalancing event received.'); - }) - - consumer.on('rebalanced', () => { - logger.info('Rebalanced event received.'); - }); - - var producer = new Producer(kafkaClient); - producer.on('error', (err) => { - logger.error('Unexpected kafka producer error: %s', err.message); - logger.error(err.stack); + kafkaClient = new Kafka({ + brokers: kafkaBootstrapServers.split(','), + logLevel: logLevel.INFO, + logCreator: KafkaJsWinstonLogCreator }); - var messageProcessor = new JsInvokeMessageProcessor(producer); + consumer = kafkaClient.consumer({ groupId: 'js-executor-group' }); + producer = kafkaClient.producer(); + const messageProcessor = new JsInvokeMessageProcessor(producer); + await consumer.connect(); + await producer.connect(); + await consumer.subscribe({ topic: kafkaRequestTopic}); - producer.on('ready', () => { - consumer.on('message', (message) => { + logger.info('Started ThingsBoard JavaScript Executor Microservice.'); + await consumer.run({ + eachMessage: async ({ topic, partition, message }) => { messageProcessor.onJsInvokeMessage(message); - }); - logger.info('Started ThingsBoard JavaScript Executor Microservice.'); + }, }); } catch (e) { @@ -84,21 +62,41 @@ var kafkaClient; } })(); -process.on('exit', function () { +process.on('exit', () => { exit(0); }); -function exit(status) { +async function exit(status) { logger.info('Exiting with status: %d ...', status); - if (kafkaClient) { - logger.info('Stopping Kafka Client...'); - var _kafkaClient = kafkaClient; - kafkaClient = null; - _kafkaClient.close(() => { - logger.info('Kafka Client stopped.'); + if (consumer) { + logger.info('Stopping Kafka Consumer...'); + var _consumer = consumer; + consumer = null; + try { + await _consumer.disconnect(); + logger.info('Kafka Consumer stopped.'); + await disconnectProducer(); process.exit(status); - }); + } catch (e) { + logger.info('Kafka Consumer stop error.'); + await disconnectProducer(); + process.exit(status); + } } else { process.exit(status); } } + +async function disconnectProducer() { + if (producer) { + logger.info('Stopping Kafka Producer...'); + var _producer = producer; + producer = null; + try { + await _producer.disconnect(); + logger.info('Kafka Producer stopped.'); + } catch (e) { + logger.info('Kafka Producer stop error.'); + } + } +} From 3c3ad1ac383ef9b7dd2eea78ebeaf17c3836e455 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2019 16:01:10 +0200 Subject: [PATCH 097/120] Improve Remote JS Invoke Service statistics --- .../service/script/RemoteJsInvokeService.java | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 4ba4efb4b3..b913889a24 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.script; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.Getter; @@ -31,11 +32,13 @@ import org.thingsboard.server.kafka.TbKafkaRequestTemplate; import org.thingsboard.server.kafka.TbKafkaSettings; import org.thingsboard.server.kafka.TbNodeIdProvider; +import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -79,6 +82,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger kafkaInvokeMsgs = new AtomicInteger(0); private final AtomicInteger kafkaEvalMsgs = new AtomicInteger(0); private final AtomicInteger kafkaFailedMsgs = new AtomicInteger(0); + private final AtomicInteger kafkaTimeoutMsgs = new AtomicInteger(0); @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { @@ -86,8 +90,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { int invokeMsgs = kafkaInvokeMsgs.getAndSet(0); int evalMsgs = kafkaEvalMsgs.getAndSet(0); int failed = kafkaFailedMsgs.getAndSet(0); - log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}]", - kafkaPushedMsgs.getAndSet(0), invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed); + int timedOut = kafkaTimeoutMsgs.getAndSet(0); + log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", + kafkaPushedMsgs.getAndSet(0), invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); } } @@ -145,16 +150,28 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.trace("Post compile request for scriptId [{}]", scriptId); ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); kafkaPushedMsgs.incrementAndGet(); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable JsInvokeProtos.RemoteJsResponse result) { + kafkaEvalMsgs.incrementAndGet(); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { + kafkaTimeoutMsgs.incrementAndGet(); + } + kafkaFailedMsgs.incrementAndGet(); + } + }); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); if (compilationResult.getSuccess()) { - kafkaEvalMsgs.incrementAndGet(); scriptIdToNameMap.put(scriptId, functionName); scriptIdToBodysMap.put(scriptId, scriptBody); return compiledScriptId; } else { - kafkaFailedMsgs.incrementAndGet(); log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); throw new RuntimeException(compilationResult.getErrorDetails()); } @@ -182,16 +199,27 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setInvokeRequest(jsRequestBuilder.build()) .build(); - ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); kafkaPushedMsgs.incrementAndGet(); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable JsInvokeProtos.RemoteJsResponse result) { + kafkaInvokeMsgs.incrementAndGet(); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { + kafkaTimeoutMsgs.incrementAndGet(); + } + kafkaFailedMsgs.incrementAndGet(); + } + }); return Futures.transform(future, response -> { JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse(); if (invokeResult.getSuccess()) { - kafkaInvokeMsgs.incrementAndGet(); return invokeResult.getResult(); } else { - kafkaFailedMsgs.incrementAndGet(); log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw new RuntimeException(invokeResult.getErrorDetails()); } From da81db88d897373e8d168d3ce3d8dff6af433340 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2019 16:45:27 +0200 Subject: [PATCH 098/120] Fix Rule Engine tests. --- .../rule/engine/action/TbAlarmNodeTest.java | 1 + .../rule/engine/filter/TbJsFilterNodeTest.java | 11 ++++++----- .../rule/engine/transform/TbTransformMsgNodeTest.java | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 3402c89b44..fb07951368 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -157,6 +157,7 @@ public class TbAlarmNodeTest { verify(ctx, times(1)).getJsExecutor(); verify(ctx).getAlarmService(); verify(ctx, times(3)).getDbCallbackExecutor(); + verify(ctx).logJsEvalRequest(); verify(ctx).getTenantId(); verify(alarmService).findLatestByOriginatorAndType(tenantId, originator, "SomeType"); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 49853abd9a..d30a2f8626 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -19,6 +19,7 @@ import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -59,10 +60,10 @@ public class TbJsFilterNodeTest { initWithScript(); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); - when(scriptEngine.executeFilter(msg)).thenReturn(false); + when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); - verify(ctx).getJsExecutor(); + verify(ctx).getDbCallbackExecutor(); verify(ctx).tellNext(msg, "False"); } @@ -72,7 +73,7 @@ public class TbJsFilterNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); - when(scriptEngine.executeFilter(msg)).thenThrow(new ScriptException("error")); + when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); node.onMsg(ctx, msg); @@ -85,10 +86,10 @@ public class TbJsFilterNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); - when(scriptEngine.executeFilter(msg)).thenReturn(true); + when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); - verify(ctx).getJsExecutor(); + verify(ctx).getDbCallbackExecutor(); verify(ctx).tellNext(msg, "True"); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 279864b951..f6464caf15 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -65,10 +65,10 @@ public class TbTransformMsgNodeTest { TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); - when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg); + when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(transformedMsg)); node.onMsg(ctx, msg); - verify(ctx).getJsExecutor(); + verify(ctx).getDbCallbackExecutor(); ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx).tellNext(captor.capture(), eq(SUCCESS)); TbMsg actualMsg = captor.getValue(); @@ -86,7 +86,7 @@ public class TbTransformMsgNodeTest { RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); mockJsExecutor(); - when(scriptEngine.executeUpdate(msg)).thenThrow(new IllegalStateException("error")); + when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("error"))); node.onMsg(ctx, msg); verifyError(msg, "error", IllegalStateException.class); From c08a5e02f88884ff05d8bbbe5cc70c4a12b999a5 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2019 18:55:22 +0200 Subject: [PATCH 099/120] Improve device state check interval --- .../service/state/DefaultDeviceStateService.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index e8336f2093..c3c8dc7803 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -63,14 +63,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; @@ -132,7 +125,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @Value("${state.defaultStateCheckIntervalInSec}") @Getter - private long defaultStateCheckIntervalInSec; + private int defaultStateCheckIntervalInSec; @Value("${state.persistToTelemetry:false}") @Getter @@ -153,7 +146,7 @@ public class DefaultDeviceStateService implements DeviceStateService { // Should be always single threaded due to absence of locks. queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor()); queueExecutor.submit(this::initStateFromDB); - queueExecutor.scheduleAtFixedRate(this::updateState, defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS); + queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); //TODO: schedule persistence in v2.1; } From b2e7a8588537ce7f615c07ae9c5f70980e56d4cf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2019 18:56:50 +0200 Subject: [PATCH 100/120] Improve remote js invoke service --- .../server/service/script/RemoteJsInvokeService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index b913889a24..1fcab45c41 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -148,7 +148,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .build(); log.trace("Post compile request for scriptId [{}]", scriptId); - ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); + ListenableFuture future = kafkaTemplate.post(UUID.randomUUID().toString(), jsRequestWrapper); kafkaPushedMsgs.incrementAndGet(); Futures.addCallback(future, new FutureCallback() { @Override @@ -199,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setInvokeRequest(jsRequestBuilder.build()) .build(); - ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); + ListenableFuture future = kafkaTemplate.post(UUID.randomUUID().toString(), jsRequestWrapper); kafkaPushedMsgs.incrementAndGet(); Futures.addCallback(future, new FutureCallback() { @Override @@ -237,7 +237,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setReleaseRequest(jsRequest) .build(); - ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); + ListenableFuture future = kafkaTemplate.post(UUID.randomUUID().toString(), jsRequestWrapper); JsInvokeProtos.RemoteJsResponse response = future.get(); JsInvokeProtos.JsReleaseResponse compilationResult = response.getReleaseResponse(); From 327c4218d961699c60e9c815a52c2b447fda30a7 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Wed, 4 Dec 2019 12:14:48 +0200 Subject: [PATCH 101/120] Feature/rest client (added constructors and removed method resetPassword) (#2231) * added methods from admin-controller, alarm-controller, asset-controller, audit-log-controller * refactored rest client and added methods from auth controller * added methods from component-descriptor-controller * added methods from customer controller * added methods from dashboard controller * added methods from device controller * refactored url pageLink params * added methods from entity relation controller * added methods from entity view controller * refactored * added methods from event controller * added methods from rpc controller * added methods from rule chain controller * added methods from telemetry controller * added methods from tenant controller * added methods from user controller * added methods from widgets bundle controller * added methods from widget type controller * created method refreshToken * moved classes SecuritySettings, UserPasswordPolicy, ClaimRequest, UpdateMessage, to common module, and added "/api" to urls where this part was missing * refactored * added constructors * removed method resetPassword * removed method checkResetToken * refactoring methods where the parameter is an array * refactored --- .../server/controller/EventController.java | 3 -- .../thingsboard/client/tools/RestClient.java | 46 ++++++++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index 8d08179409..1812a4f90f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -24,7 +24,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -32,9 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; @RestController @RequestMapping("/api") diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 5296ea8129..e2d1326380 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -18,7 +18,6 @@ package org.thingsboard.client.tools; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; @@ -86,10 +85,9 @@ import java.util.Optional; /** * @author Andrew Shvayka */ -@RequiredArgsConstructor public class RestClient implements ClientHttpRequestInterceptor { private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; - protected final RestTemplate restTemplate = new RestTemplate(); + protected final RestTemplate restTemplate; protected final String baseURL; private String token; private String refreshToken; @@ -97,8 +95,19 @@ public class RestClient implements ClientHttpRequestInterceptor { private final static String TIME_PAGE_LINK_URL_PARAMS = "limit={limit}&startTime={startTime}&endTime={endTime}&ascOrder={ascOrder}&offset={offset}"; private final static String TEXT_PAGE_LINK_URL_PARAMS = "limit={limit}&textSearch{textSearch}&idOffset={idOffset}&textOffset{textOffset}"; + protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; + public RestClient(String baseURL) { + this.restTemplate = new RestTemplate(); + this.baseURL = baseURL; + } + + public RestClient(RestTemplate restTemplate, String baseURL) { + this.restTemplate = restTemplate; + this.baseURL = baseURL; + } + @Override public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { HttpRequest wrapper = new HttpRequestWrapper(request); @@ -553,7 +562,7 @@ public class RestClient implements ClientHttpRequestInterceptor { HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - assetIds).getBody(); + String.join(",", assetIds)).getBody(); } public List findByQuery(AssetSearchQuery query) { @@ -672,7 +681,8 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public ResponseEntity checkActivateToken(String activateToken) { + public ResponseEntity checkActivateToken(String userId) { + String activateToken = getActivateToken(userId); return restTemplate.getForEntity(baseURL + "/api/noauth/activate?activateToken={activateToken}", String.class, activateToken); } @@ -682,10 +692,6 @@ public class RestClient implements ClientHttpRequestInterceptor { restTemplate.exchange(URI.create(baseURL + "/api/noauth/resetPasswordByEmail"), HttpMethod.POST, new HttpEntity<>(resetPasswordByEmailRequest), Object.class); } - public ResponseEntity checkResetToken(String resetToken) { - return restTemplate.getForEntity(baseURL + "/api/noauth/resetPassword?resetToken={resetToken}", String.class, resetToken); - } - public Optional activateUser(String userId, String password) { ObjectNode activateRequest = objectMapper.createObjectNode(); activateRequest.put("activateToken", getActivateToken(userId)); @@ -702,22 +708,6 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Optional resetPassword(String resetToken, String resetPassword) { - ObjectNode resetPasswordRequest = objectMapper.createObjectNode(); - resetPasswordRequest.put("resetToken", resetToken); - resetPasswordRequest.put("resetPassword", resetPassword); - try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/resetPassword", resetPasswordRequest, JsonNode.class); - return Optional.ofNullable(jsonNode.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } - } - public Optional getComponentDescriptorByClazz(String componentDescriptorClazz) { try { ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/api/component/{componentDescriptorClazz}", ComponentDescriptor.class, componentDescriptorClazz); @@ -747,7 +737,7 @@ public class RestClient implements ClientHttpRequestInterceptor { HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - componentTypes).getBody(); + String.join(",", componentTypes)).getBody(); } public Optional getCustomerById(String customerId) { @@ -1108,7 +1098,7 @@ public class RestClient implements ClientHttpRequestInterceptor { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - deviceIds).getBody(); + String.join(",", deviceIds)).getBody(); } public List findByQuery(DeviceSearchQuery query) { @@ -1785,7 +1775,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/tenants?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenants?" + TEXT_PAGE_LINK_URL_PARAMS, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { From 599e79fc27b99ab0a43061b99f8e0fa14a2a01be Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Dec 2019 17:18:39 +0200 Subject: [PATCH 102/120] Rest Client fix --- .../thingsboard/client/tools/RestClient.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index e2d1326380..e8dd83d1fe 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -1962,16 +1962,30 @@ public class RestClient implements ClientHttpRequestInterceptor { private void addPageLinkToParam(Map params, TimePageLink pageLink) { params.put("limit", String.valueOf(pageLink.getLimit())); - params.put("startTime", String.valueOf(pageLink.getStartTime())); - params.put("endTime", String.valueOf(pageLink.getEndTime())); + if (pageLink.getStartTime() != null) { + params.put("startTime", String.valueOf(pageLink.getStartTime())); + } + if (pageLink.getEndTime() != null) { + params.put("endTime", String.valueOf(pageLink.getEndTime())); + } params.put("ascOrder", String.valueOf(pageLink.isAscOrder())); - params.put("offset", pageLink.getIdOffset().toString()); + if (pageLink.getIdOffset() != null) { + params.put("offset", pageLink.getIdOffset().toString()); + } } private void addPageLinkToParam(Map params, TextPageLink pageLink) { params.put("limit", String.valueOf(pageLink.getLimit())); - params.put("textSearch", pageLink.getTextSearch()); - params.put("idOffset", pageLink.getIdOffset().toString()); - params.put("textOffset", pageLink.getTextOffset()); + if (pageLink.getTextSearch() != null) { + params.put("textSearch", pageLink.getTextSearch()); + } + + if (pageLink.getIdOffset() != null) { + params.put("idOffset", pageLink.getIdOffset().toString()); + + } + if (pageLink.getTextOffset() != null) { + params.put("textOffset", pageLink.getTextOffset()); + } } } From a28b10dcbf9c386b0f65b03320d4fc6fb14d4c8f Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Dec 2019 18:32:08 +0200 Subject: [PATCH 103/120] RestClient PageLinks --- .../thingsboard/client/tools/RestClient.java | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index e8dd83d1fe..cfcc2e7921 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -82,6 +82,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import static org.springframework.util.StringUtils.isEmpty; + /** * @author Andrew Shvayka */ @@ -93,8 +95,6 @@ public class RestClient implements ClientHttpRequestInterceptor { private String refreshToken; private final ObjectMapper objectMapper = new ObjectMapper(); - private final static String TIME_PAGE_LINK_URL_PARAMS = "limit={limit}&startTime={startTime}&endTime={endTime}&ascOrder={ascOrder}&offset={offset}"; - private final static String TEXT_PAGE_LINK_URL_PARAMS = "limit={limit}&textSearch{textSearch}&idOffset={idOffset}&textOffset{textOffset}"; protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; @@ -421,14 +421,43 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("fetchOriginator", String.valueOf(fetchOriginator)); addPageLinkToParam(params, pageLink); + String urlParams = getUrlParams(pageLink); return restTemplate.exchange( - baseURL + "/api/alarm/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}&fetchOriginator={fetchOriginator}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/alarm/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}&fetchOriginator={fetchOriginator}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, params).getBody(); } + private String getUrlParams(TimePageLink pageLink) { + String urlParams = "limit={limit}&ascOrder={ascOrder}"; + if (pageLink.getStartTime() != null) { + urlParams += "&startTime={startTime}"; + } + if (pageLink.getEndTime() != null) { + urlParams += "&endTime={endTime}"; + } + if (pageLink.getIdOffset() != null) { + urlParams += "&offset={offset}"; + } + return urlParams; + } + + private String getUrlParams(TextPageLink pageLink) { + String urlParams = "limit={limit}&ascOrder={ascOrder}"; + if (!isEmpty(pageLink.getTextSearch())) { + urlParams += "&textSearch={textSearch}"; + } + if (!isEmpty(pageLink.getIdOffset())) { + urlParams += "&idOffset={idOffset}"; + } + if (!isEmpty(pageLink.getTextOffset())) { + urlParams += "&textOffset={textOffset}"; + } + return urlParams; + } + public Optional getHighestAlarmSeverity(String entityType, String entityId, String searchStatus, String status) { Map params = new HashMap<>(); params.put("entityType", entityType); @@ -518,7 +547,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( - baseURL + "/tenant/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/tenant/assets?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -546,7 +575,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( - baseURL + "/api/customer/{customerId}/assets?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/assets?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -590,7 +619,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/api/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -606,7 +635,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/api/audit/logs/user/{userId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/user/{userId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -623,7 +652,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/api/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -638,7 +667,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( - baseURL + "/api/audit/logs?actionTypes={actionTypes}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/audit/logs?actionTypes={actionTypes}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -783,7 +812,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> customer = restTemplate.exchange( - baseURL + "/api/customers?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customers?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -943,7 +972,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("tenantId", tenantId); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenant/{tenantId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/{tenantId}/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -955,7 +984,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenant/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -968,7 +997,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("customerId", customerId); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/customer/{customerId}/dashboards?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1058,7 +1087,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenant/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/devices?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1085,7 +1114,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/customer/{customerId}/devices?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/devices?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1362,7 +1391,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/customer/{customerId}/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/entityViews?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1375,7 +1404,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("type", type); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenant/entityViews?type={type}&" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/entityViews?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1415,7 +1444,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1431,7 +1460,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/events/{entityType}/{entityId}?tenantId={tenantId}&" + TIME_PAGE_LINK_URL_PARAMS, + baseURL + "/api/events/{entityType}/{entityId}?tenantId={tenantId}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1510,7 +1539,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/ruleChains" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/ruleChains" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1775,7 +1804,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenants?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenants?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1835,7 +1864,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/tenant/{tenantId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/tenant/{tenantId}/users?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1849,7 +1878,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/customer/{customerId}/users?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/customer/{customerId}/users?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1892,7 +1921,7 @@ public class RestClient implements ClientHttpRequestInterceptor { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/widgetsBundles?" + TEXT_PAGE_LINK_URL_PARAMS, + baseURL + "/api/widgetsBundles?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { From 0b6a68ad3055a15fb0c0a4a4af33b539d6812707 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Dec 2019 18:47:57 +0200 Subject: [PATCH 104/120] RestClient PageLinks --- .../src/main/java/org/thingsboard/client/tools/RestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index cfcc2e7921..0af6d6d962 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -445,7 +445,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } private String getUrlParams(TextPageLink pageLink) { - String urlParams = "limit={limit}&ascOrder={ascOrder}"; + String urlParams = "limit={limit}"; if (!isEmpty(pageLink.getTextSearch())) { urlParams += "&textSearch={textSearch}"; } From 00ff95bccfb0b210c0e9072385bd7011b8e146a9 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Dec 2019 15:21:41 +0200 Subject: [PATCH 105/120] added support batch telemetry --- .../src/main/resources/thingsboard.yml | 12 ++ ...sKvEntity.java => AbstractTsKvEntity.java} | 4 +- .../sqlts/timescale/TimescaleTsKvEntity.java | 4 +- .../server/dao/model/sqlts/ts/TsKvEntity.java | 4 +- .../dao/model/sqlts/ts/TsKvLatestEntity.java | 4 +- .../server/dao/sql/TbSqlBlockingQueue.java | 4 +- .../dao/sqlts/AbstractInsertRepository.java | 9 ++ .../sqlts/AbstractLatestInsertRepository.java | 4 + .../AbstractTimeseriesInsertRepository.java | 8 +- .../timescale/TimescaleInsertRepository.java | 120 +++++++++++++++++- .../timescale/TimescaleTimeseriesDao.java | 47 ++++++- .../sqlts/ts/HsqlLatestInsertRepository.java | 7 + .../ts/HsqlTimeseriesInsertRepository.java | 7 + .../server/dao/sqlts/ts/JpaTimeseriesDao.java | 73 +++++++++-- .../sqlts/ts/PsqlLatestInsertRepository.java | 119 +++++++++++++++++ .../ts/PsqlTimeseriesInsertRepository.java | 56 ++++++++ 16 files changed, 455 insertions(+), 27 deletions(-) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{AbsractTsKvEntity.java => AbstractTsKvEntity.java} (97%) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 16de3fd708..8be9a8a1c5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -204,6 +204,18 @@ sql: batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" + ts: + batch_size: "${SQL_TS_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" + ts_latest: + batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" + ts_timescale: + batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index d8c0e4ef0a..4c8a2606d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -35,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM @Data @MappedSuperclass -public abstract class AbsractTsKvEntity { +public abstract class AbstractTsKvEntity { protected static final String SUM = "SUM"; protected static final String AVG = "AVG"; @@ -80,7 +80,7 @@ public abstract class AbsractTsKvEntity { protected static boolean isAllNull(Object... args) { for (Object arg : args) { - if(arg != null) { + if (arg != null) { return false; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java index 3427c928f4..753e2c10fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java @@ -21,7 +21,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.ColumnResult; @@ -115,7 +115,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F resultSetMapping = "timescaleCountMapping" ) }) -public final class TimescaleTsKvEntity extends AbsractTsKvEntity implements ToData { +public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToData { @Id @Column(name = TENANT_ID_COLUMN) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index c5b9237f13..dab344cb44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.Entity; @@ -37,7 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Entity @Table(name = "ts_kv") @IdClass(TsKvCompositeKey.class) -public final class TsKvEntity extends AbsractTsKvEntity implements ToData { +public final class TsKvEntity extends AbstractTsKvEntity implements ToData { @Id @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java index 3c1f735834..fb558d7b87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.Entity; @@ -37,7 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Entity @Table(name = "ts_kv_latest") @IdClass(TsKvLatestCompositeKey.class) -public final class TsKvLatestEntity extends AbsractTsKvEntity implements ToData { +public final class TsKvLatestEntity extends AbstractTsKvEntity implements ToData { @Id @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java index 7630ccdaad..6e894fb382 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java @@ -92,8 +92,8 @@ public class TbSqlBlockingQueue implements TbSqlQueue { }); logExecutor.scheduleAtFixedRate(() -> { - log.info("Attributes queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", - queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); + log.info("[{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", + params.getLogName(), queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java index 919ab5314d..a4cd67abdc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -15,8 +15,11 @@ */ package org.thingsboard.server.dao.sqlts; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -57,6 +60,12 @@ public abstract class AbstractInsertRepository { @PersistenceContext protected EntityManager entityManager; + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + protected TransactionTemplate transactionTemplate; + protected static String getInsertOrUpdateStringHsql(String tableName, String constraint, String value, String nullValues) { return "MERGE INTO " + tableName + " USING(VALUES :entity_type, :entity_id, :key, :ts, :" + value + ") A (entity_type, entity_id, key, ts, " + value + ") ON " + constraint + " WHEN MATCHED THEN UPDATE SET " + tableName + "." + value + " = A." + value + ", " + tableName + ".ts = A.ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, " + value + ") VALUES (A.entity_type, A.entity_id, A.key, A.ts, A." + value + ")"; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java index a31b0e395b..e9b10eafa3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java @@ -19,11 +19,15 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import java.util.List; + @Repository public abstract class AbstractLatestInsertRepository extends AbstractInsertRepository { public abstract void saveOrUpdate(TsKvLatestEntity entity); + public abstract void saveOrUpdate(List entities); + protected void processSaveOrUpdate(TsKvLatestEntity entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { if (entity.getBooleanValue() != null) { saveOrUpdateBoolean(entity, requestBoolValue); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java index 6f1b9b1ed3..4787cd7a46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java @@ -17,13 +17,17 @@ package org.thingsboard.server.dao.sqlts; import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; + +import java.util.List; @Repository -public abstract class AbstractTimeseriesInsertRepository extends AbstractInsertRepository { +public abstract class AbstractTimeseriesInsertRepository extends AbstractInsertRepository { public abstract void saveOrUpdate(T entity); + public abstract void saveOrUpdate(List entities); + protected void processSaveOrUpdate(T entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { if (entity.getBooleanValue() != null) { saveOrUpdateBoolean(entity, requestBoolValue); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index 11f4ea4b5d..8493703275 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -15,13 +15,22 @@ */ package org.thingsboard.server.dao.sqlts.timescale; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + @TimescaleDBTsDao @PsqlDao @Repository @@ -30,14 +39,123 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String BATCH_UPDATE = + "UPDATE tenant_ts_kv SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_type = ? AND entity_id = ? and key = ? and ts = ?"; + + + private static final String INSERT_OR_UPDATE = + "INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TimescaleTsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(1, entities.get(i).getBooleanValue()); + } else { + ps.setNull(1, Types.BOOLEAN); + } + + ps.setString(2, replaceNullChars(entities.get(i).getStrValue())); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(3, entities.get(i).getLongValue()); + } else { + ps.setNull(3, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(4, entities.get(i).getDoubleValue()); + } else { + ps.setNull(4, Types.DOUBLE); + } + + ps.setString(5, entities.get(i).getTenantId()); + ps.setString(6, entities.get(i).getEntityId()); + ps.setString(7, entities.get(i).getKey()); + ps.setLong(8, entities.get(i).getTs()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + + int updatedCount = 0; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + updatedCount++; + } + } + + List insertEntities = new ArrayList<>(updatedCount); + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + insertEntities.add(entities.get(i)); + } + } + + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getTenantId()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }); + } + }); + } + @Override protected void saveOrUpdateBoolean(TimescaleTsKvEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 844f22a31c..961f545567 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -36,11 +37,16 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -66,6 +72,39 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Autowired private AbstractTimeseriesInsertRepository insertRepository; + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Value("${sql.ts_timescale.batch_size:1000}") + private int batchSize; + + @Value("${sql.ts_timescale.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.ts_timescale.stats_print_interval_ms:1000}") + private long statsPrintIntervalMs; + + private TbSqlBlockingQueue queue; + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + .logName("TS Timescale") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .build(); + queue = new TbSqlBlockingQueue<>(params); + queue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); + } + } + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -126,11 +165,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - log.trace("Saving entity to timescale db: {}", entity); - return insertService.submit(() -> { - insertRepository.saveOrUpdate(entity); - return null; - }); + return queue.add(entity); } @Override @@ -209,7 +244,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { List> result = new ArrayList<>(); timescaleTsKvEntities.forEach(entity -> { - if(entity != null && entity.isNotEmpty()) { + if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityIdStr); entity.setTenantId(tenantIdStr); entity.setKey(key); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java index 84250406d8..07650396f2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -22,6 +22,8 @@ import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.util.List; + @SqlTsDao @HsqlDao @Repository @@ -40,6 +42,11 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + + } + @Override protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java index 927bcd2443..8dbefd4443 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java @@ -22,6 +22,8 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.util.List; + @SqlTsDao @HsqlDao @Repository @@ -40,6 +42,11 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + + } + @Override protected void saveOrUpdateBoolean(TsKvEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java index b70b59604f..7c198d9a73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -38,6 +39,9 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; @@ -46,6 +50,8 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.SqlTsDao; import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -73,6 +79,63 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese @Autowired private AbstractLatestInsertRepository insertLatestRepository; + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Value("${sql.ts.batch_size:1000}") + private int tsBatchSize; + + @Value("${sql.ts.batch_max_delay:100}") + private long tsMaxDelay; + + @Value("${sql.ts.stats_print_interval_ms:1000}") + private long tsStatsPrintIntervalMs; + + @Value("${sql.ts_latest.batch_size:1000}") + private int tsLatestBatchSize; + + @Value("${sql.ts_latest.batch_max_delay:100}") + private long tsLatestMaxDelay; + + @Value("${sql.ts_latest.stats_print_interval_ms:1000}") + private long tsLatestStatsPrintIntervalMs; + + private TbSqlBlockingQueue tsQueue; + private TbSqlBlockingQueue tsLatestQueue; + + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() + .logName("TS") + .batchSize(tsBatchSize) + .maxDelay(tsMaxDelay) + .statsPrintIntervalMs(tsStatsPrintIntervalMs) + .build(); + tsQueue = new TbSqlBlockingQueue<>(tsParams); + tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); + + TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder() + .logName("TS Latest") + .batchSize(tsLatestBatchSize) + .maxDelay(tsLatestMaxDelay) + .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs) + .build(); + tsLatestQueue = new TbSqlBlockingQueue<>(tsLatestParams); + tsLatestQueue.init(logExecutor, v -> insertLatestRepository.saveOrUpdate(v)); + } + + @PreDestroy + private void destroy() { + if (tsQueue != null) { + tsQueue.destroy(); + } + + if (tsLatestQueue != null) { + tsLatestQueue.destroy(); + } + } + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -266,10 +329,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); log.trace("Saving entity: {}", entity); - return insertService.submit(() -> { - insertRepository.saveOrUpdate(entity); - return null; - }); + return tsQueue.add(entity); } @Override @@ -288,10 +348,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return insertService.submit(() -> { - insertLatestRepository.saveOrUpdate(latestEntity); - return null; - }); + return tsLatestQueue.add(latestEntity); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java index 5d50bf0dd9..92252e9d18 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java @@ -15,13 +15,22 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + @SqlTsDao @PsqlDao @Repository @@ -35,11 +44,121 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String BATCH_UPDATE = + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_type = ? AND entity_id = ? and key = ?"; + + + private static final String INSERT_OR_UPDATE = + "INSERT INTO ts_kv_latest (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_type, entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TsKvLatestEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setLong(1, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(2, entities.get(i).getBooleanValue()); + } else { + ps.setNull(2, Types.BOOLEAN); + } + + ps.setString(3, replaceNullChars(entities.get(i).getStrValue())); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(4, entities.get(i).getLongValue()); + } else { + ps.setNull(4, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(5, entities.get(i).getDoubleValue()); + } else { + ps.setNull(5, Types.DOUBLE); + } + + ps.setString(6, entities.get(i).getEntityType().name()); + ps.setString(7, entities.get(i).getEntityId()); + ps.setString(8, entities.get(i).getKey()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + + int updatedCount = 0; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + updatedCount++; + } + } + + List insertEntities = new ArrayList<>(updatedCount); + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + insertEntities.add(entities.get(i)); + } + } + + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, insertEntities.get(i).getEntityType().name()); + ps.setString(2, insertEntities.get(i).getEntityId()); + ps.setString(3, insertEntities.get(i).getKey()); + ps.setLong(4, insertEntities.get(i).getTs()); + ps.setLong(9, insertEntities.get(i).getTs()); + + if (insertEntities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, insertEntities.get(i).getBooleanValue()); + ps.setBoolean(10, insertEntities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(10, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(11, replaceNullChars(entities.get(i).getStrValue())); + + + if (insertEntities.get(i).getLongValue() != null) { + ps.setLong(7, insertEntities.get(i).getLongValue()); + ps.setLong(12, insertEntities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(12, Types.BIGINT); + } + + if (insertEntities.get(i).getDoubleValue() != null) { + ps.setDouble(8, insertEntities.get(i).getDoubleValue()); + ps.setDouble(13, insertEntities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(13, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }); + } + }); + } + @Override protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java index 0baea27d7b..edc37822b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; @@ -22,6 +23,11 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + @SqlTsDao @PsqlDao @Repository @@ -35,6 +41,10 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "INSERT INTO ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_type, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -83,4 +93,50 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo .setParameter("dbl_v", entity.getDoubleValue()) .executeUpdate(); } + + @Override + public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + } } \ No newline at end of file From e31b7ba98867f6f03cacbe73ee3792bee87478e3 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 5 Dec 2019 15:54:00 +0200 Subject: [PATCH 106/120] Change Executor type for Transport Layer --- .../thingsboard/server/common/transport/TransportContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index ad6baf0258..99e1f9b7df 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -47,7 +47,7 @@ public class TransportContext { @PostConstruct public void init() { - executor = Executors.newCachedThreadPool(); + executor = Executors.newWorkStealingPool(50); } @PreDestroy From c564510342c54f3101fc3c0ce120a2294fcfe259 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Dec 2019 16:40:45 +0200 Subject: [PATCH 107/120] added realization for Hsql --- .../sqlts/ts/HsqlLatestInsertRepository.java | 47 +++++++++++++++++ .../ts/HsqlTimeseriesInsertRepository.java | 50 ++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java index 07650396f2..12c9f9c0a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; @@ -22,6 +23,9 @@ import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.List; @SqlTsDao @@ -37,6 +41,16 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, HSQL_LATEST_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, HSQL_LATEST_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (ts_kv_latest.entity_type=T.entity_type " + + "AND ts_kv_latest.entity_id=T.entity_id " + + "AND ts_kv_latest.key=T.key) " + + "WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v " + + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; + @Override public void saveOrUpdate(TsKvLatestEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -44,7 +58,40 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { @Override public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + } + + ps.setString(6, entities.get(i).getStrValue()); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + } + } + @Override + public int getBatchSize() { + return entities.size(); + } + }); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java index 8dbefd4443..5d27b0d06f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; @@ -22,6 +23,9 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.List; @SqlTsDao @@ -34,9 +38,20 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, HSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, HSQL_ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V , HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, HSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (ts_kv.entity_type=T.entity_type " + + "AND ts_kv.entity_id=T.entity_id " + + "AND ts_kv.key=T.key " + + "AND ts_kv.ts=T.ts) " + + "WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v " + + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; + @Override public void saveOrUpdate(TsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -44,7 +59,40 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo @Override public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + } + + ps.setString(6, entities.get(i).getStrValue()); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + } + } + @Override + public int getBatchSize() { + return entities.size(); + } + }); } @Override From 1b5a8ac6fc837d8e291a61f6a6a776de6349def4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 5 Dec 2019 18:02:12 +0200 Subject: [PATCH 108/120] Improve remote transport service to process rule engine callbacks asynchronously --- .../service/RemoteTransportService.java | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java index ec7f491fed..632ce16bc1 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java @@ -320,28 +320,19 @@ public class RemoteTransportService extends AbstractTransportService { send(sessionInfo, toRuleEngineMsg, callback); } - private static class TransportCallbackAdaptor implements Callback { - private final TransportServiceCallback callback; - - TransportCallbackAdaptor(TransportServiceCallback callback) { - this.callback = callback; - } - - @Override - public void onCompletion(RecordMetadata metadata, Exception exception) { - if (exception == null) { - if (callback != null) { - callback.onSuccess(null); - } - } else { - if (callback != null) { - callback.onError(exception); + private void send(SessionInfoProto sessionInfo, ToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback callback) { + ruleEngineProducer.send(getRoutingKey(sessionInfo), toRuleEngineMsg, (metadata, exception) -> { + if (callback != null) { + if (exception == null) { + this.transportCallbackExecutor.submit(() -> { + callback.onSuccess(null); + }); + } else { + this.transportCallbackExecutor.submit(() -> { + callback.onError(exception); + }); } } - } - } - - private void send(SessionInfoProto sessionInfo, ToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback callback) { - ruleEngineProducer.send(getRoutingKey(sessionInfo), toRuleEngineMsg, new TransportCallbackAdaptor(callback)); + }); } } From f312bc308951bf3fb84daa8dac1b2dc4bbf2c3c2 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 6 Dec 2019 11:45:38 +0200 Subject: [PATCH 109/120] refactored --- .../src/main/resources/thingsboard.yml | 6 +- .../timescale/TimescaleInsertRepository.java | 128 +++++------------- 2 files changed, 38 insertions(+), 96 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8be9a8a1c5..fcf62a659b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -213,9 +213,9 @@ sql: batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" ts_timescale: - batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" - batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" - stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" + batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index 8493703275..b47c79cb98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sqlts.timescale; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; @@ -28,7 +26,6 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; -import java.util.ArrayList; import java.util.List; @TimescaleDBTsDao @@ -57,101 +54,46 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor @Override public void saveOrUpdate(List entities) { - transactionTemplate.execute(new TransactionCallbackWithoutResult() { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(1, entities.get(i).getBooleanValue()); - } else { - ps.setNull(1, Types.BOOLEAN); - } - - ps.setString(2, replaceNullChars(entities.get(i).getStrValue())); - - if (entities.get(i).getLongValue() != null) { - ps.setLong(3, entities.get(i).getLongValue()); - } else { - ps.setNull(3, Types.BIGINT); - } - - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(4, entities.get(i).getDoubleValue()); - } else { - ps.setNull(4, Types.DOUBLE); - } - - ps.setString(5, entities.get(i).getTenantId()); - ps.setString(6, entities.get(i).getEntityId()); - ps.setString(7, entities.get(i).getKey()); - ps.setLong(8, entities.get(i).getTs()); - } - - @Override - public int getBatchSize() { - return entities.size(); - } - }); - - int updatedCount = 0; - for (int i = 0; i < result.length; i++) { - if (result[i] == 0) { - updatedCount++; - } + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getTenantId()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); } - List insertEntities = new ArrayList<>(updatedCount); - for (int i = 0; i < result.length; i++) { - if (result[i] == 0) { - insertEntities.add(entities.get(i)); - } + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); } + } - jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - ps.setString(1, entities.get(i).getTenantId()); - ps.setString(2, entities.get(i).getEntityId()); - ps.setString(3, entities.get(i).getKey()); - ps.setLong(4, entities.get(i).getTs()); - - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(5, entities.get(i).getBooleanValue()); - ps.setBoolean(9, entities.get(i).getBooleanValue()); - } else { - ps.setNull(5, Types.BOOLEAN); - ps.setNull(9, Types.BOOLEAN); - } - - ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); - ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); - - - if (entities.get(i).getLongValue() != null) { - ps.setLong(7, entities.get(i).getLongValue()); - ps.setLong(11, entities.get(i).getLongValue()); - } else { - ps.setNull(7, Types.BIGINT); - ps.setNull(11, Types.BIGINT); - } - - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(8, entities.get(i).getDoubleValue()); - ps.setDouble(12, entities.get(i).getDoubleValue()); - } else { - ps.setNull(8, Types.DOUBLE); - ps.setNull(12, Types.DOUBLE); - } - } - - @Override - public int getBatchSize() { - return insertEntities.size(); - } - }); + @Override + public int getBatchSize() { + return entities.size(); } }); } From fc986a5b64c7d86dbe8d1c55fd62e58f77ee154e Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Fri, 6 Dec 2019 15:14:17 +0100 Subject: [PATCH 110/120] Set SpringResourceLoader to public --- .../server/config/ThingsboardMessageConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java index fc25af3a56..56c0bc1974 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java @@ -84,7 +84,7 @@ public class ThingsboardMessageConfiguration { } @Slf4j - static class SpringResourceLoader extends org.apache.velocity.runtime.resource.loader.ResourceLoader { + public static class SpringResourceLoader extends org.apache.velocity.runtime.resource.loader.ResourceLoader { public static final String NAME = "spring"; From 424c137d3a5b1e0f2f76971f6bf155a8f9e06a39 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 5 Dec 2019 16:30:46 +0200 Subject: [PATCH 111/120] Add greek language --- ui/src/app/locale/locale.constant-cs_CZ.json | 3 +- ui/src/app/locale/locale.constant-de_DE.json | 3 +- ui/src/app/locale/locale.constant-el_GR.json | 2606 ++++++++++++++++++ ui/src/app/locale/locale.constant-en_US.json | 3 +- ui/src/app/locale/locale.constant-es_ES.json | 3 +- ui/src/app/locale/locale.constant-fr_FR.json | 3 +- ui/src/app/locale/locale.constant-it_IT.json | 3 +- ui/src/app/locale/locale.constant-ru_RU.json | 3 +- ui/src/app/locale/locale.constant-tr_TR.json | 3 +- ui/src/app/locale/locale.constant-uk_UA.json | 3 +- 10 files changed, 2624 insertions(+), 9 deletions(-) create mode 100644 ui/src/app/locale/locale.constant-el_GR.json diff --git a/ui/src/app/locale/locale.constant-cs_CZ.json b/ui/src/app/locale/locale.constant-cs_CZ.json index 053b6c5d24..848dd89b7c 100644 --- a/ui/src/app/locale/locale.constant-cs_CZ.json +++ b/ui/src/app/locale/locale.constant-cs_CZ.json @@ -1651,7 +1651,8 @@ "tr_TR": "Turkish", "fa_IR": "Persian", "uk_UA": "Ukrainian", - "cs_CZ": "Česky" + "cs_CZ": "Česky", + "el_GR": "Řečtina" } } } diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index cea54826b6..d313770b89 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -1699,7 +1699,8 @@ "tr_TR": "Türkisch", "fa_IR": "Persisch", "uk_UA": "Ukrainisch", - "cs_CZ": "Tschechisch" + "cs_CZ": "Tschechisch", + "el_GR": "Griechisch" } } } diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json new file mode 100644 index 0000000000..b590c70e47 --- /dev/null +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -0,0 +1,2606 @@ +{ + "access": { + "unauthorized": "Χωρίς δικαιώματα πρόσβασης", + "unauthorized-access": "Μη εξουσιοδοτημένη πρόσβαση", + "unauthorized-access-text": "Θα πρέπει να συνδεθείτε για να έχετε πρόσβαση σε αυτόν τον πόρο!", + "access-forbidden": "Απαγορεύεται η πρόσβαση!", + "access-forbidden-text": "Δεν έχετε δικαιώματα πρόσβασης σε αυτήν την τοποθεσία!
Συνδεθείτε με διαφορετικό όνομα χρήστη, αν εξακολουθείτε να θέλετε να έχετε πρόσβαση σε αυτήν την τοποθεσία.", + "refresh-token-expired": "Η περίοδος χρήσης έχει λήξει", + "refresh-token-failed": "Δεν είναι δυνατή η ανανέωση της περιόδου χρήσης", + "permission-denied": "Άρνηση πρόσβασης!", + "permission-denied-text": "Δεν έχετε δικαίωμα εκτέλεσης αυτής της λειτουργίας!" + }, + "action": { + "activate": "Ενεργοποίηση", + "suspend": "Αναστολή", + "save": "Αποθήκευση", + "saveAs": "Αποθήκευση ως", + "cancel": "Ακύρωση", + "ok": "OK", + "delete": "Διαγραφή", + "add": "Προσθήκη", + "yes": "Ναι", + "no": "Όχι", + "update": "Ενημέρωση", + "remove": "Διαγραφή", + "search": "Αναζήτηση", + "clear-search": "Καθαρισμός Αναζήτησης", + "assign": "Ανάθεση", + "unassign": "Ακύρωση Ανάθεσης", + "share": "Διαμοιρασμός", + "make-private": "Ιδιωτικό", + "apply": "Εφαρμογή", + "apply-changes": "Εφααρμογή Αλλαγών", + "edit-mode": "Λειτουργία Επεξεργασίας", + "enter-edit-mode": "Έναρξη Επεξεργασίας", + "decline-changes": "Απόρριψη Αλλαγών", + "close": "Κλείσιμο", + "back": "Πίσω", + "run": "Εκτέλεση", + "sign-in": "Σύνδεση!", + "edit": "Επεξεργασία", + "view": "Επισκόπηση", + "create": "Δημιουργία", + "drag": "Drag", + "refresh": "Ανανέωση", + "undo": "Αναίρεση", + "copy": "Αντιγραφή", + "paste": "Επικόλληση", + "copy-reference": "Αντιγραφή παραπομπής", + "paste-reference": "Επικόλληση παραπομπής", + "import": "Εισαγωγή", + "export": "Εξαγωγή", + "share-via": "Διαμοίραση μέσω {{provider}}", + "move": "Μετακίνηση", + "select": "Επιλογή", + "continue": "Συνέχεια" + }, + "aggregation": { + "aggregation": "Συνάθροιση", + "function": "Συνάρτηση συνάθροισης δεδομένων", + "limit": "Μέγιστες τιμές", + "group-interval": "Διάστημα ομαδοποίησης", + "min": "Min", + "max": "Max", + "avg": "Μέσος Όρος", + "sum": "Άθροισμα", + "count": "Καταμέτρηση", + "none": "Κανένα" + }, + "admin": { + "general": "Γενικά", + "general-settings": "Γενικές Ρυθμίσεις", + "outgoing-mail": "Διακομιστής Αλληλογραφίας", + "outgoing-mail-settings": "Διακομιστής Εξερχόμενης Αλληλογραφίας", + "system-settings": "Ρυθμίσεις Συστήματος", + "test-mail-sent": "Το δοκιμαστικό μήνυμα στάλθηκε με επιτυχία!", + "base-url": "Βασική διεύθυνση URL", + "base-url-required": "Απαιτείται ορισμός Base URL.", + "mail-from": "Αποστολέας", + "mail-from-required": "Απαιτείται βασική διεύθυνση URL.", + "smtp-protocol": "Πρωτόκολλο SMTP", + "smtp-host": "SMTP host", + "smtp-host-required": "Απαιτείται ορισμός SMTP host.", + "smtp-port": "Θύρα SMTP", + "smtp-port-required": "Πρέπει να εισάγετε SMTP port.", + "smtp-port-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη SMTP port.", + "timeout-msec": "Timeout (msec)", + "timeout-required": "Απαιτείται τιμή Timeout.", + "timeout-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη τιμή timeout.", + "enable-tls": "Ενεργοποίηση TLS", + "send-test-mail": "Αποστολή δοκιμαστικού μηνύματος", + "use-system-mail-settings": "Χρήση των ρυθμίσεων διακομιστή αλληλογραφίας συστήματος", + "mail-templates": "Πρότυπα αλληλογραφίας", + "mail-template-settings": "Ρυθμίσεις προτύπων αλληλογραφίας", + "use-system-mail-template-settings": "Χρήση προτύπων ηλεκτρονικού ταχυδρομείου συστήματος", + "mail-template": { + "mail-template": "Πρότυπο αλληλογραφίας", + "test": "Δοκιμαστικό μήνυμα ηλεκτρονικού ταχυδρομείου", + "activation": "Μήνυμα ενεργοποίησης λογαριασμού", + "account-activated": "Μήνυμα επιτυχούς ενεργοποίησης λογαριασμού", + "reset-password": "Μήνυμα επαναφοράς κωδικού πρόσβασης", + "password-was-reset": "Μήνυμα επαναφοράς κωδικού πρόσβασης", + "user-activated": "Μήνυμα ενεργοποίησης χρήστη", + "user-registered": "Μήνυμα καταχώρησης χρήστη" + }, + "mail-subject": "Θέμα μηνύματος (subject)", + "mail-body": "Κυρίως μήνυμα (body)" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarms", + "select-alarm": "Επιλογή alarm", + "no-alarms-matching": "Δεν βρέθηκαν alarms σχετικά με '{{entity}}'.", + "alarm-required": "Απαιτείται Alarm", + "alarm-status": "Κατάσταση Alarm", + "search-status": { + "ANY": "'Ολες", + "ACTIVE": "Ενεργό", + "CLEARED": "Εκκαθαρίστηκε", + "ACK": "Επιβεβαιώθηκε", + "UNACK": "Χωρίς επιβεβαίωση" + }, + "display-status": { + "ACTIVE_UNACK": "Ενεργό χωρίς επιβεβαίωση", + "ACTIVE_ACK": "Ενεργό επιβεβαιωμένο", + "CLEARED_UNACK": "Εκκαθαρίστηκε χωρίς επιβεβαίωση", + "CLEARED_ACK": "Εκκαθαρίστηκε επιβεβαιωμένο" + }, + "no-alarms-prompt": "Δεν βρέθηκαν alarm", + "created-time": "Ώρα δημιουργίας", + "type": "Τύπος", + "severity": "Βαρύτητα", + "originator": "Δημιουργός", + "originator-type": "Τύπος δημιουργού", + "details": "Λεπτομέρειες", + "status": "Κατάσταση", + "alarm-details": "Λεπτομέρειες Alarm", + "start-time": "Ώρα έναρξης", + "end-time": "Ώρα λήξης", + "ack-time": "Ώρα επιβεβαίωσης", + "clear-time": "Ώρα εκκαθάρισης", + "severity-critical": "Critical", + "severity-major": "Κρίσιμο", + "severity-minor": "Ασήμαντο", + "severity-warning": "Προειδοποίηση", + "severity-indeterminate": "Απροσδιόριστο", + "acknowledge": "Επιβεβαίωση", + "clear": "Εκκαθάριση", + "search": "Αναζήτηση alarm", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } επιλέχθηκαν", + "no-data": "Δεν υπάρχουν δεδομένα για εμφάνιση", + "polling-interval": "Διάστημα δειγματοληψίας alarm(sec)", + "polling-interval-required": "Απαιτείται ορισμός διαστήματος δειγματοληψίας alarm.", + "min-polling-interval-message": "Η ελάχιστη επιτρεπόμενη τιμή διαστήματος δειγματοληψίας alarm είναι 1 sec.", + "aknowledge-alarms-title": "Επιβεβαίωση { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Είστε σίγουρος ότι θέλετε να επιβεβαιώσετε { count, plural, 1 {1 alarm} other {# alarms} };", + "aknowledge-alarm-title": "Επιβεβαίωση Alarm", + "aknowledge-alarm-text": "Είστε σίγουρος ότι θέλετε να επιβεβαιώσετε το Alarm?", + "clear-alarms-title": "Εκκαθάριση { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Είστε σίγουρος ότι θέλετε να εκκαθαρίσετε { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Εκκαθάριση Alarm", + "clear-alarm-text": "Είστε σίγουρος ότι θέλετε να εκκαθαρίσετε το Alarm?", + "alarm-status-filter": "Φίλτρο κατάστασης Alarm" + }, + "alias": { + "add": "Προσθήκη ψευδωνύμου", + "edit": "Επεξεργασία ψευδωνύμου", + "name": "Ψευδώνυμο", + "name-required": "Απαιτείται Ψευδώνυμο", + "duplicate-alias": "Το ψευδώνυμο υπάρχει ήδη.", + "filter-type-single-entity": "Απλή Οντότητα", + "filter-type-entity-group": "Ομάδα Οντοτήτων", + "filter-type-entity-list": "Λίστα Οντοτήτων", + "filter-type-entity-name": "Όνομα Οντότητας", + "filter-type-entity-group-list": "Λίστα ομάδας Οντοτήτων", + "filter-type-entity-group-name": "Όνομα ομάδας Οντοτήτων", + "filter-type-state-entity": "Οντότητα από την κατάσταση του dashboard", + "filter-type-state-entity-description": "Οντότητα που λαμβάνεται από τις παραμέτρους κατάστασης του dashboard", + "filter-type-asset-type": "Τύπος Asset", + "filter-type-asset-type-description": "Assets του τύπου '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Assets του τύπου '{{assetType}}' με όνομα που αρχίζει από '{{prefix}}'", + "filter-type-device-type": "Τύπος Συσκευής", + "filter-type-device-type-description": "Συσκευές του τύπου '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Συσκευές του τύπου '{{deviceType}}' με όνομα που αρχίζει από '{{prefix}}'", + "filter-type-entity-view-type": "Τύπος προβολής Οντοτήτων", + "filter-type-entity-view-type-description": "Τύπος προβολής Οντοτήτων '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Τύπος προβολής Οντοτήτων '{{entityView}}' με όνομα που αρχίζει από '{{prefix}}'", + "filter-type-relations-query": "Ερώτημα Σχέσεων", + "filter-type-relations-query-description": "{{entities}} που έχουν σχέση {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Ερώτημα αναζήτησης Asset", + "filter-type-asset-search-query-description": "Asset με τύπο {{assetTypes}} που έχουν σχέση {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Ερώτημα αναζήτησης Συσκευών", + "filter-type-device-search-query-description": "Συσκευές με τύπο {{deviceTypes}} που έχουν σχέση {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Ερώτημα αναζήτησης Όψεων Οντοτήτων", + "filter-type-entity-view-search-query-description": "Όψεις Οντοτήτων με τύπο {{entityViewTypes}} που έχουν σχέση {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter": "Φίλτρο Οντοτήτων", + "resolve-multiple": "Διακανονισμός ως πολλαπλές οντότητες", + "filter-type": "Είδος Φίλτρου", + "filter-type-required": "Απαιτείται καθορισμός του Είδους Φίλτρου.", + "entity-filter-no-entity-matched": "Δεν βρέθηκαν οντότητες που ταιριάζουν με το συγκεκριμένο φίλτρο.", + "no-entity-filter-specified": "Δεν έχει οριστεί Φίλτρο Οντοτήτων", + "root-state-entity": "Χρήση της οντότητας κατάστασης του dashboard ως ρίζα", + "group-state-entity": "Χρήση της οντότητας κατάστασης του dashboard ως ομάδας οντοτήτων", + "root-entity": "Οντότητα ρίζας", + "state-entity-parameter-name": "Όνομα παραμέτρου οντότητας κατάστασης", + "default-state-entity": "Προεπιλεγμένη οντότητα κατάστασης", + "default-state-entity-group": "Προεπιλεγμένη ομάδα καταστάσεων οντοτήτων", + "default-entity-parameter-name": "Εξ ορισμού", + "max-relation-level": "Μέγιστος βαθμός συσχέτισης", + "unlimited-level": "Απεριόριστος βαθμός", + "state-entity": "Οντότητα κατάστασης του Dashboard", + "entities-of-group-state-entity": "Οντότητες από την ομάδα οντοτήτων κατάστασης του dashboard", + "all-entities": "Όλες οι Οντότητες", + "any-relation": "Οποιαδήποτε" + }, + "asset": { + "asset": "Asset", + "assets": "Assets", + "management": "Διαχείριση Asset", + "view-assets": "Προβολή Asset", + "add": "Προσθήκη Asset", + "assign-to-customer": "Ανάθεση σε Πελάτη", + "assign-asset-to-customer": "Ανάθεση Asset(s) σε Πελάτη", + "assign-asset-to-customer-text": "Επιλέξτε τα Asset που θα αναθέσετε στον πελάτη", + "no-assets-text": "Δεν βρέθηκαν Asset", + "assign-to-customer-text": "Παρακαλώ επιλέξτε πελάτη για να αναθέσετε το Asset(s)", + "public": "Δημόσιο", + "assignedToCustomer": "Συνδεδεμένο με πελάτη", + "make-public": "Κάνε το Asset δημόσιο", + "make-private": "Κάνε το Asset ιδιωτικό", + "unassign-from-customer": "Αποσύνδεση από τον πελάτη", + "delete": "Διαγραφή Αsset", + "asset-public": "Το Asset είναι δημόσιο", + "asset-type": "Τύπος Asset", + "asset-type-required": "Απαιτείται ορισμός είδους Asset.", + "select-asset-type": "Επιλογή τύπου Αsset", + "enter-asset-type": "Εισαγωγή τύπου Αsset", + "any-asset": "Οποιοδήποτε Αsset", + "no-asset-types-matching": "Δεν βρέθηκαν τύποι Asset που να τιαιριάζουν με '{{entitySubtype}}'.", + "asset-type-list-empty": "Δεν επιλέχθηκε τύπος Asset.", + "asset-types": "Τύποι Asset", + "name": "Όνομα", + "name-required": "Απαιτείται Όνομα.", + "description": "Περιγραφή", + "type": "Τύπος", + "type-required": "Απαιτείται Τύπος.", + "details": "Λεπτομέρειες", + "events": "Γεγονότα", + "add-asset-text": "Προσθήκη νέου Asset", + "asset-details": "Λεπτομέρειες Asset", + "assign-assets": "Ανάθεση Assets", + "assign-assets-text": "Ανάθεση { count, plural, 1 {1 asset} other {# assets} } σε πελάτη", + "delete-assets": "Διαγραφή Assets", + "unassign-assets": "Αποσύνδεση Assets", + "unassign-assets-action-title": "Αποσύνδεση { count, plural, 1 {1 asset} other {# assets} } από πελάτη", + "assign-new-asset": "Ανάθεση νέου Asset", + "delete-asset-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το Asset '{{assetName}}'?", + "delete-asset-text": "Προσοχή!, Μετά την επιβεβαίωση, το Asset και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-assets-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε { count, plural, 1 {1 asset} other {# assets} };", + "delete-assets-action-title": "Διαγραφή { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Προσοχή! Μετά την επιβεβαίωση, όλα τα επιλεγμένα Asset και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "make-public-asset-title": "Είστε βέβαιοι ότι θέλετε να κάνετε το Asset '{{assetName}}' δημόσιο;", + "make-public-asset-text": "Μετά την επιβεβαίωση, το Asset και όλα τα στοιχεία του θα είναι προσβάσιμα από άλλους.", + "make-private-asset-title": "Είστε βέβαιοι ότι θέλετε να κάνετε το Asset '{{assetName}}' ιδιωτικό;", + "make-private-asset-text": "Μετά την επιβεβαίωση, το Asset και όλα τα στοιχεία του θα είναι ιδιωτικά και μη προσβάσιμα από άλλους.", + "unassign-asset-title": "Είστε βέβαιοι ότι θέλετε να αποσυνδέσετε το Asset '{{assetName}}';", + "unassign-asset-text": "Μετά την επιβεβαίωση, το Asset θα αποσυνδεθεί και δεν θα είναι προσβάσιμο από τον πελάτη.", + "unassign-asset": "Αποσύνδεση Asset", + "unassign-assets-title": "Είστε βέβαιοι ότι θέλετε να αποσυνδέσετε { count, plural, 1 {1 asset} other {# assets} };", + "unassign-assets-text": "Μετά την επιβεβαίωση, όλα τα επιλεγμένα Asset θα αποσυνδεθούν και δεν θα είναι προσβάσιμα από τον πελάτη.", + "copyId": "Αντιγραφή Asset Id", + "idCopiedMessage": "Το Asset Id αντιγράφηκε στο πρόχειρο", + "select-asset": "Επιλογή Asset", + "no-assets-matching": "Δεν βρέθηκαν Asset που να ταιριάζουν με '{{entity}}'.", + "asset-required": "Απαιτείται Asset", + "name-starts-with": "Όνομα Asset που ξεκινάει με", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } επιλέχθηκαν", + "search": "Αναζήτηση Asset", + "select-group-to-add": "Επιλέξτε ομάδα στόχο για να προσθέσετε επιλεγμένα Asset", + "select-group-to-move": "Επιλέξτε ομάδα στόχο για να μετακινήσετε επιλεγμένα assets", + "remove-assets-from-group": "Είστε βέβαιοι ότι θέλετε να καταργήσετε { count, plural, 1 {1 asset} other {# assets} } από την ομάδα '{entityGroup}';", + "group": "Ομάδα Asset", + "list-of-groups": "{ count, plural, 1 {One asset group} other {List of # asset groups} }", + "group-name-starts-with": "Ομάδες Asset των οποίων τα ονόματα ξεκινούν με '{{prefix}}'", + "import": "Εισαγωγή Asset", + "asset-file": "Αρχείο Asset" + }, + "attribute": { + "attributes": "Χαρακτηριστικά", + "latest-telemetry": "Τελευταία τηλεμετρία", + "attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας", + "scope-latest-telemetry": "Τελευταία τηλεμετρία", + "scope-client": "Χαρακτηριστικά Client", + "scope-server": "Χαρακτηριστικά Server", + "scope-shared": "Κοινόχρηστα Χαρακτηριστικά", + "add": "Προσθήκη Χαρακτηριστικού", + "add-attribute-prompt": "Προσθέστε Χαρακτηριστικό", + "key": "Όνομα", + "last-update-time": "Ώρα τελευταίας ενημέρωσης", + "key-required": "Απαιτείται μεταβλητή χαρακτηριστικού.", + "value": "Τιμή", + "value-required": "Απαιτείται ορισμός τιμής χαρακτηριστικού.", + "delete-attributes-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε { count, plural, 1 {1 attribute} other {# attributes} };", + "delete-attributes-text": "Προσοχή! Μετά την επιβεβαίωση θα αφαιρεθούν όλα τα επιλεγμένα χαρακτηριστικά.", + "delete-attributes": "Διαγραφή χαρακτηριστικών", + "enter-attribute-value": "Καταχωρίστε την τιμή του χαρακτηριστικού", + "show-on-widget": "Εμφάνιση σε widget", + "widget-mode": "Είδος λειτουργίας widget", + "next-widget": "Επόμενο widget", + "prev-widget": "Προηγούμενο widget", + "add-to-dashboard": "Προσθήκη σε dashboard", + "add-widget-to-dashboard": "Προσθήκη widget στο dashboard", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } επιλέχθηκαν", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } επιλέχθηκαν" + }, + "audit-log": { + "audit": "Καταγραφή", + "audit-logs": "Ημερολόγια Καταγραφής", + "timestamp": "Ώρα", + "entity-type": "Είδος Οντότητας", + "entity-name": "Όνομα Οντότητας", + "user": "Χρήστης", + "type": "Τύπος", + "status": "Κατάσταση", + "details": "Λεπτομέρειες", + "type-added": "Προστέθηκε", + "type-deleted": "Διαγράφηκε", + "type-updated": "Ενημερώθηκε", + "type-attributes-updated": "Τα χαρακτηριστικά ενημερώθηκαν", + "type-attributes-deleted": "Τα χαρακτηριστικά διαγράφηκαν", + "type-rpc-call": "Κλήση RPC", + "type-credentials-updated": "Τα διαπιστευτήρια ενημερώθηκαν", + "type-assigned-to-customer": "Ανατέθηκε σε Πελάτη", + "type-unassigned-from-customer": "Αποσυνδέθηκε από Πελάτη", + "type-activated": "Ενεργοποιήθηκε", + "type-suspended": "Μπήκε σε αναστολή", + "type-credentials-read": "Τα διαπιστευτήρια διαβάστηκαν", + "type-attributes-read": "Τα χαρακτηριστικά διαβάστηκαν", + "type-added-to-entity-group": "Προστέθηκε στην ομάδα", + "type-removed-from-entity-group": "Αφαιρέθηκε από την ομάδα", + "type-relation-add-or-update": "Relation updated", + "type-relation-delete": "Η συσχέτιση ενημερώθηκε", + "type-relations-delete": "Όλες οι συσχετίσεις διαγράφηκαν", + "type-alarm-ack": "Επιβεβαιώθηκε", + "type-alarm-clear": "Εκκαθαρίστηκε", + "type-rest-api-rule-engine-call": "Κλήση Rule engine REST API", + "type-made-public": "Έγινε δημόσιο", + "type-made-private": "Έγινε ιδιωτικό", + "status-success": "Επιτυχία", + "status-failure": "Αποτυχία", + "audit-log-details": "΄Λεπτομέρειες καταγραφής", + "no-audit-logs-prompt": "Δεν βρέθηκαν αρχεία καταγραφής", + "action-data": "Δεδομένα ενεργειών", + "failure-details": "Λεπτομέρειες αποτυχίας", + "search": "Αναζήτηση αρχείων καταγραφής", + "clear-search": "΄Καθαρισμός αναζήτησης" + }, + "confirm-on-exit": { + "message": "Έχετε μη αποθηκευμένες αλλαγές. Είστε βέβαιοι ότι θέλετε να φύγετε από τη σελίδα;", + "html-message": "Έχετε μη αποθηκευμένες αλλαγές.
Είστε βέβαιοι ότι θέλετε να φύγετε από τη σελίδα;", + "title": "Μη αποθηκευμένες αλλαγές" + }, + "contact": { + "country": "Χώρα", + "city": "Πόλη", + "state": "Περιφέρεια", + "postal-code": "Ταχυδρομικός Κώδικας", + "postal-code-invalid": "Μη έγκυρη μορφή ταχυδρομικού κώδικα.", + "address": "Διεύθυνση (γραμμή 1)", + "address2": "Διεύθυνση (γραμμή 2)", + "phone": "Τηλέφωνο", + "email": "Email", + "no-address": "Χωρίς διεύθυνση" + }, + "common": { + "username": "Όνομα χρήστη", + "password": "Κωδικός πρόσβασης", + "enter-username": "Εισάγετε Όνομα χρήστη", + "enter-password": "Εισάγετε Κωδικό πρόσβασης", + "enter-search": "Αναζήτηση" + }, + "converter": { + "converter": "Μετατροπέας δεδομένων", + "converters": "Μετατροπείς δεδομένων", + "select-converter": "Επιλογή μετατροπέα δεδομένων", + "no-converters-matching": "Δεν βρέθηκαν μετατροπείς δεδομένων για '{{entity}}'.", + "converter-required": "Απαιτείται ορισμός μετατροπέα δεδομένων", + "delete": "Διαγραφή μετατροπέα", + "management": "Διαχείριση μετατροπέων δεδομένων", + "add-converter-text": "Προσθήκη νέου μετατροπέα δεδομένων", + "no-converters-text": "Δεν βρέθηκαν μετατροπείς δεδομένων", + "selected-converters": "{ count, plural, 1 {1 data converter} other {# data converters} } επιλέχθηκαν", + "delete-converter-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον μετατροπέα δεδομένων '{{converterName}}'?", + "delete-converter-text": "Προσέξτε, μετά την επιβεβαίωση, ο μετατροπέας δεδομένων και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-converters-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε { count, plural, 1 {1 data converter} other {# data converters} };", + "delete-converters-action-title": "Διαγραφή { count, plural, 1 {1 data converter} other {# data converters} }", + "delete-converters-text": "Προσοχή! Μετά την επιβεβαίωση, όλοι οι επιλεγμένοι μετατροπείς δεδομένων και όλα τα σχετικά δεδομένα θα καταστούν μη ανακτήσιμα.", + "events": "Γεγονότα", + "add": "Προσθήκη μετατροπέα δεδομένων", + "converter-details": "Λεπτομέρειες μετατροπέα δεδομένων", + "details": "Λεπτομέρειες", + "copyId": "Αντιγραφή ID μετατροπέα δεδομένων", + "idCopiedMessage": "Το ID του μετατροπέα δεδομένων αντιγράφηκε στο πρόχειρο.", + "debug-mode": "Λειτουργία εντοπισμού σφαλμάτων", + "name": "Όνομα", + "name-required": "Απαιτείται Όνομα.", + "description": "Περιγραφή", + "decoder": "Αποκωδικοποιητής", + "encoder": "Kωδικοποιητής", + "test-decoder-fuction": "Δοκιμή λειτουργίας αποκωδικοποιητή", + "test-encoder-fuction": "Δοκιμή λειτουργίας κωδικοποιητή", + "decoder-input-params": "Είσοδο παραμέτρων αποκωδικοποιητή", + "encoder-input-params": "Είσοδο παραμέτρων κωδικοποιητή", + "payload": "Φορτίο", + "payload-content-type": "Τύπος περιεχόμενου φορτίου", + "payload-content": "Περιεχόμενο φορτίο", + "message": "Μήνυμα", + "message-type": "Τύπος μηνύματος", + "message-type-required": "Απαιτείται τύπος μηνύματος", + "test": "Δοκιμή", + "metadata": "Μεταδεδομένα", + "metadata-required": "Οι καταχωρίσεις μεταδεδομένων δεν μπορούν να είναι κενές.", + "integration-metadata": "Μεταδεδομένα ενσωμάτωσης", + "integration-metadata-required": "Οι καταχωρήσεις μεταδεδομένων ενσωμάτωσης δεν μπορούν να είναι κενές.", + "output": "Έξοδος", + "import": "Εισαγωγή μετατροπέα", + "export": "Εξαγωγή μετατροπέα", + "export-failed-error": "Δεν είναι δυνατή η εξαγωγή του μετατροπέα: {{error}}", + "create-new-converter": "Δημιουργία νέου μετατροπέα", + "converter-file": "Αρχείο μετατροπέα", + "invalid-converter-file-error": "Δεν είναι δυνατή η εισαγωγή του μετατροπέα: Μη έγκυρη δομή δεδομένων μετατροπέα.", + "type": "Τύπος", + "type-required": "Απαιτείται τύπος.", + "type-uplink": "Uplink", + "type-downlink": "Downlink" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "Πελάτης", + "customers": "Πελάτες", + "management": "Διαχείριση Πελατών", + "dashboard": "Dashboard Πελάτη", + "dashboards": "Dashboards Πελάτη", + "devices": "Συσκευές Πελάτη", + "entity-views": "Προβολές Οντοτήτων ΠελάτηCustomer Entity Views", + "assets": "Assets Πελάτη", + "public-dashboards": "Δημόσια Dashboards", + "public-devices": "Δημόσιες Devices", + "public-assets": "Δημόσια Assets", + "public-entity-views": "Δημόσιες Προβολές Οντοτήτων", + "add": "Προσθήκη Πελάτη", + "delete": "Διαγραφή πελάτη", + "manage-customer-user-groups": "Διαχείριση ομάδων χρηστών πελατών", + "manage-customer-groups": "Διαχείριση ομάδων πελατών", + "manage-customer-device-groups": "Διαχείριση ομάδων συσκευών πελατών", + "manage-customer-asset-groups": "Διαχείριση ομάδων Asset πελατών", + "manage-customer-entity-view-groups": "Διαχείριση ομάδων προβολής οντότητας πελατών", + "manage-customer-dashboard-groups": "Διαχείριση ομάδων dashboard πελατών", + "manage-customer-users": "Διαχείριση χρηστών πελατών", + "manage-customers": "Διαχείριση πελατών", + "manage-customer-devices": "Διαχείρηση συσκευών πελατών", + "manage-customer-entity-views": "Διαχείριση προβολής οντότητας πελατών", + "manage-customer-dashboards": "Διαχείριση dashboards πελατών", + "manage-public-devices": "Διαχείριση δημόσιων συσκευών", + "manage-public-dashboards": "Διαχείριση δημόσιων συσκευών", + "manage-customer-assets": "Διαχείριση οντοτήτων πελατών", + "manage-public-assets": "Διαχείριση δημόσιων οντοτήτων", + "add-customer-text": "Προσθήκη νέου πελάτη", + "no-customers-text": "Δεν βρέθηκαν πελάτες", + "customer-details": "Λεπτομέρειες πελάτη", + "delete-customer-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε τον πελάτη '{{customerTitle}}'?", + "delete-customer-text": "Προσέξτε, μετά την επιβεβαίωση, ο πελάτης και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-customers-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Διαγραφή { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Προσέξτε, μετά την επιβεβαίωση, οι πελάτες και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "manage-user-groups": "Διαχείρηση ομαδών χρηστών", + "manage-asset-groups": "Διαχείρηση ομαδών οντοτήτων", + "manage-device-groups": "Διαχείρηση ομαδών συσκευών", + "manage-dashboard-groups": "Διαχείρηση ομαδών dashboards", + "manage-entity-view-groups": "Διαχείρηση ομαδών προβολής οντοτήτων", + "manage-users": "Διαχείρηση χρηστών", + "manage-assets": "Διαχείρηση οντοτήτων", + "manage-devices": "Διαχείρηση συσκευών", + "manage-dashboards": "Διαχείρηση dashboards", + "title": "Τίτλος", + "title-required": "Απαιτείται ένας τίτλος.", + "description": "Περιγραφή", + "details": "Λεπτομέρειες", + "events": "Γεγονότα", + "copyId": "Αντιγραφή ID πελάτη", + "idCopiedMessage": "Το ID του πελάτη έχει αντιγραφεί στο πρόχειρο", + "select-customer": "Επιλέξτε πελάτη", + "no-customers-matching": "Δεν βρέθηκαν πελάτες που να αντιστοιχούν σε '{{entity}}'.", + "customer-required": "Απαιτείται πελάτης", + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } επιλέχθηκαν", + "search": "Αναζήτηση πελατών", + "select-group-to-add": "Επιλέξτε ομάδα για να προσθέσετε τους επιλεγμένους πελάτες", + "select-group-to-move": "Επιλέξτε ομάδα για να μετακινήσετε τους επιλεγμένους πελάτες", + "remove-customers-from-group": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 customer} other {# customers} } από την ομάδα '{entityGroup}'?", + "group": "Ομάδα πελατών", + "list-of-groups": "{ count, plural, 1 {One customer group} other {List of # customer groups} }", + "group-name-starts-with": "Πελάτες των οποίων το όνομα αρχίζει από '{{prefix}}'", + "select-default-customer": "Επιλογή προεπιλεγμένου πελάτη", + "default-customer": "Προεπιλεγμένος πελάτης", + "default-customer-required": "Ο προεπιλεγμένος πελάτης είναι υποχρεωτικός για να είναι δυνατή η απασφαλμάτωση του dashboard από τον Tenant", + "allow-white-labeling": "Επιτρέψτε το White Labeling" + }, + "customers-hierarchy": { + "customers-hierarchy": "Ιεραρχία Πελατών", + "open-nav-tree": "Άνοιγμα διακλάδωσης", + "return-to-top-level": "Επιστροφή στο αρχικό επίπεδο" + }, + "custom-menu": { + "custom-menu": "Προσαρμοσμένο Μενού", + "custom-menu-hint": "Ορίστε το προσαρμοσμένο μενού (JSON) παρακάτω. Αυτό το JSON περιέχει μια λίστα με προσαρμοσμένα στοιχεία μενού." + }, + "custom-translation": { + "custom-translation": "Μεταφράσεις", + "translation-map": "Χάρτης μετάφρασης", + "key": "Κλειδί", + "import": "Εισαγωγή μετάφρασης", + "export": "Εξαγωγή μετάφρασης", + "export-data": "Εξαγωγή δεδομένων μετάφρασης", + "import-data": "Εισαγωγή δεδομένων μετάφρασης", + "translation-file": "Αρχείο μετάφρασης", + "invalid-translation-file-error": "Δεν είναι δυνατή η εισαγωγή αρχείου μετάφρασης: Μη έγκυρη δομή δεδομένων μετάφρασης.", + "custom-translation-hint": "Καθορίστε την προσαρμοσμένη μετάφραση (JSON) παρακάτω. Αυτό το JSON θα αντικαταστήσει την προεπιλεγμένη μετάφραση. Κάνετε Λήψη αρχείου γλώσσας για να λάβετε την υπάρχουσα μετάφραση. Μπορείτε επίσης να χρησιμοποιήσετε το ληφθέν αρχείο ως αναφορά στα διαθέσιμα ζεύγη κλειδιών-τιμών μετάφρασης.", + "download-locale-file": "Λήψη αρχείου γλώσσας" + }, + "datetime": { + "date-from": "Ημ/νία από", + "time-from": "Ώρα από", + "date-to": "Ημ/νία έως", + "time-to": "Ώρα έως" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Διαχείριση Dashboard", + "view-dashboards": "Προβολή Dashboards", + "add": "Προσθήκη Dashboard", + "assign-dashboard-to-customer": "Ανάθεση Dashboard(s) Σε Πελάτη", + "assign-dashboard-to-customer-text": "Παρακαλώ επιλέξτε τα dashboards που θέλετε να αναθέσετε σε πελάτη", + "assign-to-customer-text": "Παρακαλώ επιλέξτε τον πελάτη στον οποίο θέλετε να αναθέσετε το/τα dashboard(s)", + "assign-to-customer": "Ανάθεση σε πελάτη", + "unassign-from-customer": "Αφαίρεση από πελάτη", + "make-public": "Δημοσιοποιήση Dashboard", + "make-private": "Ιδιωτικοποίηση Dashboard", + "manage-assigned-customers": "Διαχείρηση ανατεθειμένων πελατών", + "assigned-customers": "Ανατεθειμένοι πελάτες", + "assign-to-customers": "Ανάθεση Dashboard(s) Σε Πελάτες", + "assign-to-customers-text": "Παρακαλώ επιλέξτε τους πελάτες στους οποίους θέλετε να αναθέσετε το/τα dashboard(s)", + "unassign-from-customers": "Μη Ανατεθειμένα Dashboard(s) Από Πελάτες", + "unassign-from-customers-text": "Παρακαλώ επιλέξτε τους πελάτες τους οποίους θέλετε να αφαιρέσετε από το/τα dashboard(s)", + "no-dashboards-text": "Δεν βρέθηκαν dashboards", + "no-widgets": "Δεν έχουν ρυθμιστεί widgets", + "add-widget": "Προσθέστε νέο widget", + "title": "Τίτλος", + "select-widget-title": "Επιλογή widget", + "select-widget-subtitle": "Λίστα με τους διαθέσιμους τύπους widget", + "delete": "Διαγραφή dashboard", + "title-required": "Απαιτείται ένας τίτλος.", + "description": "Περιγραφή", + "details": "Λεπτομέρειες", + "dashboard-details": "Λεπτομέρειες Dashboard", + "add-dashboard-text": "Προσθέστε νέο dashboard", + "assign-dashboards": "Αναθέστε dashboards", + "assign-new-dashboard": "Ανάθεση νέου dashboard", + "assign-dashboards-text": "Ανάθεση { count, plural, 1 {1 dashboard} other {# dashboards} } σε πελάτες", + "unassign-dashboards-action-text": "Μη ανατεθειμένο/α { count, plural, 1 {1 dashboard} other {# dashboards} } από πελάτες", + "delete-dashboards": "Διαγραφή dashboards", + "unassign-dashboards": "Αφαίρεση dashboards", + "unassign-dashboards-action-title": "Αφαίρεση { count, plural, 1 {1 dashboard} other {# dashboards} } από πελάτη", + "delete-dashboard-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε το dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Προσοχή, μετά την επιβεβαίωση, το dashboard και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-dashboards-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Διααγραφή { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Προσοχή, μετά την επιβεβαίωση, όλα τα επιλεγμένα dashboards και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "unassign-dashboard-title": "Είστε σίγουρου ότι θέλετε να αφαιρέσετε το dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Μετά την επιβεβαίωση το dashboard θα αφαιρεθεί και δεν θα είναι διαθέσιμο στον πελάτη.", + "unassign-dashboard": "Αφαίρεση dashboard", + "unassign-dashboards-title": "Είστε σίγουρου ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "Μετά την επιβεβαίωση όλα τα επιλεγμένα dashboards θα αφαιρεθούν και δεν θα είναι διαθέσιμα στον πελάτη.", + "public-dashboard-title": "Το dashboard είναι δημόσιο", + "public-dashboard-text": "Το dashboard {{dashboardTitle}} είναι δημόσιο και διαθέσιμο μέσω του παρακάτω συνδέσμου:", + "public-dashboard-notice": "Σημείωση: Μην ξεχνάτε να κάνετε δημόσιες τις συσκευές που συσχετίζονται ώστε να είναι δυνατή η πρόσβαση στα δεδομένα τους.", + "public-dashboard-link": "Σύνδεσμος δημόσιου dashboard", + "public-dashboard-link-text": "Το δημόσιο dashboard {{dashboardTitle}} είναι πλέον διαθέσιμο μέσω μέσω του παρακάτω συνδέσμου:", + "public-dashboard-link-notice": "Σημείωση: Μην ξεχνάτε να κάνετε δημόσιες τις συσκευές, τα assets και την προβολή οντοτήτων που συσχετίζονται ώστε να είναι δυνατή η πρόσβαση στα δεδομένα τους.", + "make-private-dashboard-title": "Είστε σίγουροι ότι θέλετε να κάνετε το dashboard '{{dashboardTitle}}' ιδιωτικό", + "make-private-dashboard-text": "Μετά την επιβεβαίωση το dashboard θα γίνουν ιδιωτικά και δεν θα είναι διαθέσιμα από τρίτους.", + "make-private-dashboard": "Κάνε το dashboard ιδιωτικό", + "socialshare-text": "'{{dashboardTitle}}' powered by EyeTech", + "socialshare-title": "'{{dashboardTitle}}' powered by EyeTech", + "select-dashboard": "Επιλογή dashboard", + "no-dashboards-matching": "Δεν βρέθηκαν dashboards που να αντιστοιχούν σε '{{entity}}'.", + "dashboard-required": "Απαιτείται dashboard.", + "select-existing": "Επιλέξτε ένα υπάρχον dashboard", + "create-new": "Δημιουργία νέου dashboard", + "new-dashboard-title": "Τίτλος νέου dashboard", + "open-dashboard": "Άνοιγμα dashboard", + "set-background": "Ορίστε φόντο", + "background-color": "χρώμα φόντου", + "background-image": "εικόνα φόντου", + "background-size-mode": "μέγεθος φόντου", + "no-image": "Δεν επιλέχθηκε εικόνα", + "drop-image": "Ρίξτε εδώ μια εικόνα ή κάνετε κλικ για να επιλέξετε ένα αρχείο για να ανεβεί.", + "settings": "Ρυθμίσεις", + "columns-count": "Αρίθμιση στηλών", + "columns-count-required": "Απαιτείται η αρίθμιση στηλών.", + "min-columns-count-message": "Ελάχιστος αριθμός στηλών είναι 10.", + "max-columns-count-message": "Μέγιστος αριθμός στηλών είναι 1000.", + "widgets-margins": "Περιθώριο μεταξύ widgets", + "horizontal-margin": "Οριζόντιο περιθώριο", + "horizontal-margin-required": "Απαιτείται τιμή για το οριζόντιο περιθώριο.", + "min-horizontal-margin-message": "Ελάχιστη τιμή οριζόντιου περιθωρίου είναι 0.", + "max-horizontal-margin-message": "Μέγιστη τιμή οριζόντιου περιθωρίου είναι 50.", + "vertical-margin": "Κάθετο περιθώριο", + "vertical-margin-required": "Απαιτείται τιμή για το κάθετο περιθώριο", + "min-vertical-margin-message": "Ελάχιστη τιμή καθέτου περιθωρίου είναι 0.", + "max-vertical-margin-message": "Μέγιστη τιμή καθέτου περιθωρίου είναι 50.", + "autofill-height": "Αυτόματη συμπλήρωση ύψους διάταξης", + "mobile-layout": "Ρυθμίσεις διάταξης mobile", + "mobile-row-height": "ύψος γραμμής mobile, σε px", + "mobile-row-height-required": "Απαιτείται ύψος γραμμής mobile.", + "min-mobile-row-height-message": "Ελάχιστη τιμή ύψους γραμμής mobile είναι 5.", + "max-mobile-row-height-message": "Μέγιστη τιμή ύψους γραμμής mobile είναι 200.", + "display-title": "Τίτλος εμφάνισης dashboard", + "toolbar-always-open": "Γραμμή εργαλείων πάντα ανοιχτή", + "title-color": "Χρώμα τίτλου", + "display-dashboards-selection": "Εμφάνηση επιλογής dashboards", + "display-entities-selection": "Εμφάνιση επιλογής οντοτήτων", + "display-dashboard-timewindow": "Εμφάνιση χρονικού πλαισίου", + "display-dashboard-export": "Εμφάνιση εξαγωγής", + "import": "Εισαγωγή dashboard", + "export": "Εξαγωγή dashboard", + "export-failed-error": "Δεν ήταν δυνατή η εξαγωγή dashboard: {{error}}", + "export-pdf": "Εξαγωγή ως PDF", + "export-png": "Εξαγωγή ως PNG", + "export-jpg": "Εξαγωγή ως JPEG", + "export-json-config": "Εξαγωγή ρυθμίσεις JSON", + "download-dashboard-progress": "Δημιουργία dashboard {{reportType}} ...", + "create-new-dashboard": "Δημιουργία νέου dashboard", + "dashboard-file": "Αρχείο dashboard", + "invalid-dashboard-file-error": "Δεν ήταν δυνατή η εισαγωγή dashboard: Μη έγκυρη δομή δεδομένων dashboard.", + "dashboard-import-missing-aliases-title": "Διαμορφώστε τα ψευδώνυμα που χρησιμοποιούνται από το dashboard που εισαγάγατε", + "create-new-widget": "Δημιουργία νέου widget", + "import-widget": "Εισαγωγή widget", + "widget-file": "Αρχείο widget", + "invalid-widget-file-error": "Δεν ήταν δυνατή η εισαγωγή widget: Μη έγκυρη δομή δεδομένων dashboard.", + "widget-import-missing-aliases-title": "Διαμορφώστε τα ψευδώνυμα που χρησιμοποιούνται από το widget που εισαγάγατε", + "open-toolbar": "Άνοιγμα γραμμής εργαλείων dashboard", + "close-toolbar": "Κλείσιμο γραμμής εργαλείων", + "configuration-error": "Σφάλμα ρυθμίσεων", + "alias-resolution-error-title": "Σφάλμα ρυθμίσεων ψευδωνύμων dashboard", + "invalid-aliases-config": "Δεν βρέθηκε καμία συσκευή που να ταιριάζει με κάποιο από τα φίλτρα.
Παρακαλούμε επικοινωνήστε με τον Διαχειριστή για την επίλυση του ζητήματος.", + "select-devices": "Επιλογή συσκευών", + "assignedToCustomer": "Ανατεθειμένο σε πελάτη", + "assignedToCustomers": "Ανατεθειμένο σε πελάτες", + "public": "Δημόσιο", + "public-link": "Δημόσιος σύνδεσμος", + "copy-public-link": "Αντιγραφή δημόσιου συνδέσμου", + "public-link-copied-message": "Ο δημόσιος σύνδεσμος έχει αντιγραφεί στο πρόχειρο", + "manage-states": "Διαχείρηση κατάστασης dashboard", + "states": "Κατάσταση dashboard", + "search-states": "Αναζήτηση σε κατάσταση dashboard", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } επιλεγμένα", + "edit-state": "Επεξεργασία κατάστασης dashboard", + "delete-state": "Διαγραφή κατάστασης dashboard", + "add-state": "Προσθήκη κατάστασης dashboard", + "state": "Κατάσταση Dashboard", + "state-name": "Όνομα", + "state-name-required": "Απαιτείται όνομα κατάστασης dashboard.", + "state-id": "ID Κατάστασης", + "state-id-required": "Απαιτείται ID κατάστασης dashboard.", + "state-id-exists": "Υπάρχει ήδη κατάσταση dashboard με το ίδιο ID.", + "is-root-state": "Root state", + "delete-state-title": "Διαγραφή κατάστασης dashboard", + "delete-state-text": "Είστε σίγουροι ότι θέλετε να διαγράψετε την κατάσταση dashboard με όνομα '{{stateName}}'?", + "show-details": "Εμφάνιση λεπτομερειών", + "hide-details": "Απόκρυψη λεπτομερειών", + "select-state": "Επιλογή κατάστασης", + "state-controller": "Έλεγχος κατάστασης", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } επιλεγμένα", + "search": "Αναζήτηση dashboards", + "select-group-to-add": "Επιλογή ομάδας για να προστεθεί στα επιλεγμένα dashboards", + "select-group-to-move": "Επιλογή ομάδας για να μετακινηθεί στα επιλεγμένα dashboards", + "remove-dashboards-from-group": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 dashboard} other {# dashboards} } from group '{entityGroup}'?", + "group": "Ομάδα dashboards", + "list-of-groups": "{ count, plural, 1 {One dashboard group} other {List of # dashboard groups} }", + "group-name-starts-with": "Ομάδες dashboard των οποίων το όνομα αρχίζει από '{{prefix}}'" + }, + "datakey": { + "settings": "Ρυθμίσεις", + "advanced": "Προχωρημένες", + "label": "Ετικέτα", + "color": "Χρώμα", + "units": "Ειδικό σύμβολο που εμφανίζεται δίπλα από την τιμή", + "decimals": "Αριθμός ψηφίων μετά την υποδιαστολή", + "data-generation-func": "Λειτουργία δημιουργίας δεδομένων", + "use-data-post-processing-func": "Χρησιμοποιήστε τη λειτουργία μετα-επεξεργασίας δεδομένων", + "configuration": "Ρυθμίσεις ονομάτων δεδομένων", + "timeseries": "Χρονική σειρά", + "attributes": "Ιδιώτητες", + "alarm": "Πεδία alarm", + "timeseries-required": "Απαιτείται χρονική σειρά οντοτήτων.", + "timeseries-or-attributes-required": "Απαιτείται χρονική σειρά/ιδιώτητες οντοτήτων.", + "maximum-timeseries-or-attributes": "Μέγιστο { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Απαιτούνται πεδία alarm.", + "function-types": "Τύποι λειτουργιών", + "function-types-required": "Απαιτούνται τύποι λειτουργιών.", + "maximum-function-types": "Μέγιστο { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "χρονικό σημείο της συγκεκριμένης τιμής", + "value-description": "η συγκεκριμένη τιμή", + "prev-value-description": "αποτέλεσμα της προηγούμενης λειτουργίας", + "time-prev-description": "χρονσικό σημείο της προηγούμενης τιμής", + "prev-orig-value-description": "αρχική προηγούμενη τιμή" + }, + "datasource": { + "type": "Τύπος πηγής δεδομένων", + "name": "Όνομα", + "add-datasource-prompt": "Παρακαλούμε προσθέστε πηγή δεδομένων" + }, + "details": { + "details": "Λεπτομέρειες", + "edit-mode": "Λειτουργία Επεξεργασίας", + "toggle-edit-mode": "Εναλλαγή λειτουργίας επεξεργασίας" + }, + "device": { + "device": "Συσκευή", + "device-required": "Απαιτείται ορισμός συσκευής.", + "devices": "Συσκευές", + "management": "Διαχείριση Συσκευών", + "view-devices": "Προβολή Συσκευών", + "device-alias": "Ψευδώνυμο συσκευής", + "aliases": "Ψευδώνυμα συσκευής", + "no-alias-matching": "Δεν βρέθηκε '{{alias}}'.", + "no-aliases-found": "Δεν βρέθηκαν ψευδώνυμα.", + "no-key-matching": "Δεν βρέθηκε '{{key}}'.", + "no-keys-found": "Δεν βρέθηκαν ονόματα", + "create-new-alias": "Δημιουργήστε ένα νέο ψευδώνυμο!", + "create-new-key": "Δημιουργήστε ένα νέο!", + "duplicate-alias-error": "Βρέθηκε διπλότυπο το ψευδώνυμο '{{alias}}'.
Τα ψευδώνυμα συσκευών πρέπει να είναι μοναδικά σε ένα dashboard.", + "configure-alias": "Ρύθμιση ψευδώνυμου '{{alias}}'", + "no-devices-matching": "Δεν βρέθηκε συσκευή η οποία να αντιστοιχεί σε '{{entity}}'.", + "alias": "Ψευδώνυμο", + "alias-required": "Απαιτείται ψευδώνυμο συσκευής.", + "remove-alias": "Αφαίρεση ψευδώνυμου συσκευής", + "add-alias": "Προσθήκη ψευδώνυμου συσκευής", + "name-starts-with": "Το όνομα συσκευής αρχίζει με", + "device-list": "Λίστα συσκευών", + "use-device-name-filter": "Χρήση φίλτρου", + "device-list-empty": "Δεν επιλέχθηκαν συσκευές.", + "device-name-filter-required": "Απαιτείται φίλτρο ονόματος συσκυεής.", + "device-name-filter-no-device-matched": "Δεν βρέθηκαν συσκευές οι οποίες να αρχίζουν από '{{device}}'.", + "add": "Προσθήκη συσκευής", + "assign-to-customer": "Ανάθεση σε πελάτη", + "assign-device-to-customer": "Ανάθεση Συσκευών Σε Πελάτη", + "assign-device-to-customer-text": "Παρακαλούμε επιλέξτε συσκευές προς ανάθεση σε πελάτη", + "make-public": "Δημοσιοποίηση συσκευών", + "make-private": "Ιδιωτικοποίηση συσκευών", + "no-devices-text": "Δεν βρέθηκαν συσκευές", + "assign-to-customer-text": "Παρακαλώ επιλέξτε πελάτη για να αναθέσετε τις συσκευές", + "device-details": "Λεπτομέρειες συσκευές", + "add-device-text": "Προσθήκη νέας συσκευής", + "credentials": "Διαπιστευτήρια", + "manage-credentials": "Διαχείρηση διαπιστευτηρίων", + "delete": "Διαγραφή συσκευής", + "assign-devices": "Ανάθεση συσκευών", + "assign-devices-text": "Ανάθεση { count, plural, 1 {1 device} other {# devices} } σε πελάτη", + "delete-devices": "Διαγραφή συσκευών", + "unassign-from-customer": "Αφαίρεση από πελάτη", + "unassign-devices": "Αφαίρεση συσκευών", + "unassign-devices-action-title": "Αφαίρεση { count, plural, 1 {1 device} other {# devices} } από πελάτη", + "assign-new-device": "Ανάθεση νέων συσκευών", + "make-public-device-title": "Είστε σίγουροι ότι θέλετε να κάνετε τη συσκευή '{{deviceName}}' δημόσια;", + "make-public-device-text": "Μετά από την επιβεβαίωσή σας η συσκευή και όλα τα δεδομένα της θα είναι δημόσια και διαθέσιμα σε τρίτους.", + "make-private-device-title": "Είστε σίγουροι ότι θέλετε να κάνετε τη συσκευή '{{deviceName}}' ιδιωτική;", + "make-private-device-text": "Μετά από την επιβεβαίωσή σας η συσκευή και όλα τα δεδομένα της θα είναι ιδιωτικά και δεν θα είναι διαθέσιμα σε τριτους.", + "view-credentials": "Προβολή διαπιστευτηρίων", + "delete-device-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την συσκευή '{{deviceName}}';", + "delete-device-text": "Προσοχή, μετά την επιβεβαίωση, η συσκευή και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-devices-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 device} other {# devices} };", + "delete-devices-action-title": "Διαγραφή { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Προσοχή, μετά την επιβεβαίωση, όλες οι επιλεγμένες συσκευές θα αφαιρεθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "unassign-device-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε την συσκευή '{{deviceName}}'?", + "unassign-device-text": "Μετά την επιβεβαίωση, η συσκευή θα αφαιρεθεί και δεν θα είναι προσβάσιμες από τον πελάτη.", + "unassign-device": "Αφαίρεση συσκευής", + "unassign-devices-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "Μετά την επιβεβαίωση, όλες οι επιλεγμένες συσκευές θα καταργηθούν και δεν θα είναι προσβάσιμες από τον πελάτη.", + "device-credentials": "Πιστοποιητικά συσκευής", + "credentials-type": "Τύπος διαπιστευτηρίων", + "access-token": "Διακριτικό πρόσβασης", + "access-token-required": "Απαιτείται διακριτικό πρόσβασης.", + "access-token-invalid": "Access token length must be from 1 to 20 characters.", + "rsa-key": "RSA public key", + "rsa-key-required": "Απαιτείται RSA public key.", + "secret": "Secret", + "secret-required": "Απαιτείται secret.", + "device-type": "Τύπος συσκευής", + "device-type-required": "Απαιτείται τύπος συσκεύης.", + "select-device-type": "Επιλογή τύπου συσκευής", + "enter-device-type": "Είσοδο τύπου συσκευής", + "any-device": "Οποιαδήποτε συσκευή", + "no-device-types-matching": "Δεν βρέθηκε συσκευή η οποία να αντιστοιχεί σε '{{entitySubtype}}'.", + "device-type-list-empty": "Δεν έχουν επιλεχθεί τύποι συσκευής.", + "device-types": "Τύποι συσκευής", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "label": "Ετικέτα", + "events": "Γεγονότα", + "details": "Λεπτομέρειες", + "copyId": "Αντιγραφή ID συσκευής", + "copyAccessToken": "Αντιγραφή διακριτικού διαπιστευτηρίου", + "idCopiedMessage": "Το ID της συσκευής έχει αντιγραφεί στο πρόχειρο", + "accessTokenCopiedMessage": "Το διακριτικό διαπιστευτήριο έχει αντιγραφεί στο πρόχειρο", + "assignedToCustomer": "Ανάθεση σε πελάτη", + "unable-delete-device-alias-title": "Αδύνατο να διαγραφεί το ψευδώνυμο συσκευής", + "unable-delete-device-alias-text": "Το ψευδώνυμο συσκευής '{{deviceAlias}}' δεν μπορεί να διαγραφεί όσο εξακωλουθεί να χρησιμοποιείται από τα ακόλουθα widget(s):
{{widgetsList}}", + "is-gateway": "είναι gateway", + "public": "Δημόσιο", + "device-public": "Η συσκευή είναι δημόσια", + "select-device": "Επιλογή συσκευής", + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } επιλέχθηκαν", + "search": "Αναζήτηση συσκευών", + "select-group-to-add": "Επιλέξτε την ομάδα στην οποία θα προστεθουν οι επιλεγμένες συσκευές", + "select-group-to-move": "Επιλέξτε την ομάδα στην οποία θα μετακινηθούν οι επιλεγμένες συσκευές", + "remove-devices-from-group": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 device} other {# devices} } from group '{entityGroup}'?", + "group": "Ομάδα Συσκευών", + "list-of-groups": "{ count, plural, 1 {One device group} other {List of # device groups} }", + "group-name-starts-with": "Ομάδες συσκευών των οποίων το όνομα αρχίζει από '{{prefix}}'", + "import": "Εισαγωγή συσκευής", + "device-file": "Αρχείο συσκευής" + }, + "dialog": { + "close": "Κλείσιμο διαλόγου" + }, + "direction": { + "column": "Στήλη", + "row": "Γραμμή" + }, + "error": { + "unable-to-connect": "Αδύνατον να συνδεθεί στον διακομιστή! Παρακαλούμε ελέγξτε την σύνδεσή σας στο ίντερνετ.", + "unhandled-error-code": "Μη διορθωμένος κωδικός σφάλματος: {{errorCode}}", + "unknown-error": "Άγνωστο σφάλμα" + }, + "entity": { + "entity": "Οντότητα", + "entities": "Οντότητες", + "aliases": "Ψευδώνυμα οντότητας", + "entity-alias": "Ψευδώνυμο οντότητας", + "unable-delete-entity-alias-title": "Αδύνατο να διαγραφεί το ψευδώνυμο οντότητας", + "unable-delete-entity-alias-text": "Το ψευδώνυμο οντότητας '{{entityAlias}}' δεν μπορεί να διαγραφεί όσο εξακολουθεί να χρησιμοποιείται από τα παρακάτω widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Βρέθηκε διπλότυπο το ψευδώνυμο '{{alias}}'.
Τα ψευδώνυμα οντοτήτων πρέπει να είναι μοναδικά σε ένα dashboard.", + "missing-entity-filter-error": "Λείπει φίλτρο από το ψευδώνυμο '{{alias}}'.", + "configure-alias": "Ρύθμιση ψευδώνυμου '{{alias}}'", + "alias": "Ψευδώνυμο", + "alias-required": "Απαιτείται ψευδώνυμο οντότητας.", + "remove-alias": "Αφαίρεση ψευδώνυμου οντότητας", + "add-alias": "Προσθήκη ψευδώνυμου οντότητας", + "entity-list": "Λίστα οντοτήτων", + "entity-type": "Τύπος οντοτήτων", + "entity-types": "Τύποι οντοτήτων", + "entity-type-list": "Λίστα τύπων οντοτήτων", + "any-entity": "Οποιαδήποτε οντότητα", + "enter-entity-type": "Εισαγωγή τύπου οντότητας", + "no-entities-matching": "Δεν βρέθηκαν οντότητες ο οποίες να σχετίζονται με '{{entity}}'.", + "no-entity-types-matching": "Δεν βρέθηκαν τύποι οντότητας ο οποίοι να σχετίζονται με '{{entityType}}'.", + "name-starts-with": "το όνομα αρχίζει από", + "use-entity-name-filter": "χρήση φίλτρου", + "entity-list-empty": "Δεν έχουν επιλεγεί οντότητες.", + "entity-type-list-empty": "Δεν έχουν επιλεγεί τύποι οντότητας.", + "entity-name-filter-required": "Απαιτείται φίλτρο ονόματος οντότητας.", + "entity-name-filter-no-entity-matched": "Δεν βρέθηκαν οντότητες οι οποίες αρχίζουν από '{{entity}}'.", + "all-subtypes": "Όλοι", + "select-entities": "Επιλογή οντοτήτων", + "no-aliases-found": "Δεν βρέθηκαν ψευδώνυμα.", + "no-alias-matching": "Δεν βρέθηκε '{{alias}}'.", + "create-new-alias": "Δημιουργείστε ένα νέο!", + "key": "Κλειδί", + "key-name": "όνομα κλειδιού", + "no-keys-found": "δεν βρέθηκαν κλειδιά.", + "no-key-matching": "Δεν βρέθηκε '{{key}}'.", + "create-new-key": "Δημιουργείστε ένα νέο!", + "type": "Τύπος", + "type-required": "Απαιτείται τύπος οντότητας.", + "type-device": "Συσκευή", + "type-devices": "Συσκευές", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Συσκευές των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-asset": "Asset", + "type-assets": "Assets", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Assets των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-entity-view": "Προβολή οντότητας", + "type-entity-views": "Προβολές οντότητας", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", + "entity-view-name-starts-with": "Προβολές οντότητας των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-rule": "Κανόνας", + "type-rules": "Κανόνες", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Κανόνες των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-plugin": "Πρόσθετο", + "type-plugins": "Προσθετα", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Πρόσθετα των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-tenant": "Μισθωτής", + "type-tenants": "Μισθωτές", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Μισθωτές των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-customer": "Πελάτης", + "type-customers": "Πελάτες", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Πελάτες των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-user": "Χρήστης", + "type-users": "Χρήστες", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Χρήστες των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarms", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarms των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-rulechain": "Αλυσίδα Κανόνων", + "type-rulechains": "Αλυσίδες Κανόνων", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Αλυσίδες Κανόνων των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-scheduler-event": "Προγραμματιστής", + "type-scheduler-events": "Προγραμματιστής", + "list-of-scheduler-events": "{ count, plural, 1 {One scheduler event} other {List of # scheduler events} }", + "scheduler-event-name-starts-with": "Προγραμματιστές γεγονότων των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-blob-entity": "Ογκώδη οντότητα", + "type-blob-entities": "Ογκώδεις οντότητες", + "list-of-blob-entities": "{ count, plural, 1 {One blob entity} other {List of # blob entities} }", + "blob-entity-name-starts-with": "Ογκώδεις οντότητες των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-rulenode": "Κόμβος κανόνα", + "type-rulenodes": "Κόμβοι κανόνων", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Κόμβοι κανόνων των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-current-customer": "Τρέχον Πελάτης", + "search": "Αναζήτηση Οντότητες", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } επιλεγμένα", + "entity-name": "Όνομα οντότητας", + "details": "Λεπτομέρειες οντότητας", + "no-entities-prompt": "Δεν βρέθηκαν οντότητες", + "no-data": "Δεν υπάρχουν δεδομένα προς προβολή", + "columns-to-display": "στήλες που προβάλονται", + "type-entity-group": "Ομάδα Οντότητας", + "type-converter": "Μετατροπέας Δεδομένων", + "type-converters": "Μετατροπείς Δεδομένων", + "list-of-converters": "{ count, plural, 1 {One data converter} other {List of # data converters} }", + "converter-name-starts-with": "Μετατροπείς δεδομένων των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-integration": "Ενσωμάτωση", + "type-integrations": "Ενσωματώσεις", + "list-of-integrations": "{ count, plural, 1 {One integration} other {List of # integrations} }", + "integration-name-starts-with": "Ενσωματώσεις των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-role": "Ρόλος", + "type-roles": "Ρόλοι", + "list-of-roles": "{ count, plural, 1 {One role} other {List of # roles} }", + "role-name-starts-with": "Ρόλοι των οποίων το όνομα αρχίζει από '{{prefix}}'", + "type-group-permission": "Άδεια Ομάδας" + }, + "entity-group": { + "entity-group": "Ομάδα Οντότητας", + "details": "Λεπτομέρειες", + "columns": "Στήλες", + "add-column": "Προσθήκη στήλης", + "column-value": "Τιμή", + "column-value-required": "Απαιτείται τιμή στήλης.", + "column-title": "Τίτλος", + "default-sort-order": "Προεπιλογή ταξινόμισης", + "default-sort-order-required": "Απαιτείται προεπιλογή ταξινόμισης στήλης.", + "hide-in-mobile-view": "κρυφό σε mobile", + "use-cell-style-function": "Λειτουργία χρήσης στυλ κελιού", + "use-cell-content-function": "Λειτουργία χρήσης περιεχομένου κελιού", + "edit-column": "Επεξεργασία στήλης", + "column-details": "Λεπτομέρειες στήλης", + "actions": "Ενέργιες", + "settings": "Ρυθμίσεις", + "delete": "Διαγραφή ομάδας οντοτήτων", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "add": "Προσθήκη ομάδας οντοτήτων", + "add-entity-group-text": "Προσθήκη νέας ομάδας οντοτήτων", + "no-entity-groups-text": "Δεν βρέθηκαν ομάδες οντοτήτων", + "entity-group-details": "Λεπτομέρειες ομαδών οντοτήτων", + "delete-entity-groups": "Διαγραφή ομαδών οντοτήτων", + "delete-entity-group-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την ομάδα οντοτήτων '{{entityGroupName}}'?", + "delete-entity-group-text": "Προσοχή, μετά την επιβεβαίωση, η ομάδα οντοτήτων και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-entity-groups-title": "Είστε σίγουροι ότι θέλετε να διαγραφούν { count, plural, 1 {1 entity group} other {# entity groups} }?", + "delete-entity-groups-action-title": "Διαγραφή { count, plural, 1 {1 entity group} other {# entity groups} }", + "delete-entity-groups-text": "Προσοχή, αφού ολοκληρωθεί η επιβεβαίωση, όλες οι επιλεγμένες ομάδες οντοτήτων θα αφαιρεθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "device-groups": "Ομάδες Συσκευών", + "asset-groups": "Ομάδες Asset", + "customer-groups": "Ομάδες Πελατών", + "device-group": "Ομάδα Πελατών", + "asset-group": "Ομάδα Asset", + "user-group": "Ομάδα Χρηστών", + "user-groups": "Ομάδες Χρηστών", + "customer-group": "Ομάδα Χρηστών", + "entity-view-groups": "Ομάδες Όψεων", + "entity-view-group": "Προβολή ομαδών οντοτήτων", + "dashboard-groups": "Ομάδες Dashboard", + "dashboard-group": "Ομάδα Dashboard", + "fetch-more": "Περισσότερα", + "column-type": { + "column-type": "Τύπος στήλης", + "client-attribute": "Χαρακτηριστικά Client", + "shared-attribute": "Κοινόχρηστα Χαρακτηριστικά", + "server-attribute": "Χαρακτηριστικό Server", + "timeseries": "Χρονική σειρά", + "entity-field": "Πεδίο οντότητας" + }, + "column-type-required": "Απαιτείται τύπος στήλης.", + "entity-field": { + "created-time": "Δημιουργήθηκε", + "name": "Όνομα", + "type": "Τύπος", + "assigned_customer": "Ανατεθειμένος πελάτης", + "authority": "Εξουσιοδότηση", + "first_name": "Όνομα", + "last_name": "Επίθετο", + "email": "Email", + "title": "Τίτλος", + "country": "Χώρα", + "state": "Νομός", + "city": "Πόλη", + "address": "Διεύθυνση", + "address2": "Διεύθυνση 2", + "zip": "Τ.Κ.", + "phone": "Τηλέφωνο" + }, + "sort-order": { + "asc": "Αύξουσα", + "desc": "Φθίνουσα", + "none": "Καμία" + }, + "details-mode": { + "on-row-click": "στο κλικ στη γραμμη", + "on-action-button-click": "στο κλικ στο κουμπί λεπτομέρειες", + "disabled": "μη διαθέσιμο" + }, + "change-owner": "Αλλαγή ιδιοκτήτη", + "select-target-owner": "Επιλογή ιδιοκτήτη", + "no-owners-matching": "Δεν βρέθηκε ιδιοκτήτης που να αντιστοιχεί σε '{{owner}}'.", + "target-owner-required": "Απαιτείται επιλεγμένος ιδιοκτήτης.", + "confirm-change-owner-title": "Είστε σίγουροι ότι θέλετε να αλλάξετε ιδιοκτήτη για { count, plural, 1 {1 selected entity} other {# selected entities} }?", + "confirm-change-owner-text": "Προσοχή, μετά την επιβεβαίωση, όλες οι επιλεγμένες οντότητες θα αφαιρεθούν από τον τρέχοντα ιδιοκτήτη και θα τοποθετηθούν στην ομάδα 'Όλα' του επιλεγμένου ιδιοκτήτη.", + "add-to-group": "Προσθήκη σε ομάδα", + "move-to-group": "Μετακίνηση σε ομάδα", + "select-entity-group": "Επιλογή ομάδας οντοτήτων", + "no-entity-groups-matching": "Δεν βρέθηκαν ομάδες οντοτήτων που να αντιστοιχούν με '{{entityGroup}}'.", + "target-entity-group-required": "Απαιτείται επιλεγμένη ομάδα οντοτήτων.", + "select-user-group": "Επιλογή ομάδας χρηστών", + "no-user-groups-matching": "Δεν βρέθηκαν ομάδες χρηστών οι οποίες να αντιστοιχούν σε '{{entityGroup}}'.", + "target-user-group-required": "Απαιτείται επιλεγμένη ομάδα χρηστών.", + "remove-from-group": "Αφαίρεση από ομάδα", + "group-table-title": "Τίτλος ομάδας πίνακα", + "enable-search": "Ενεργοποίηση αναζήτησης οντοτήτων", + "enable-add": "Ενεργοποίηση προσθήκης οντοτήτων", + "enable-delete": "Ενεργοποίηση διαγραφής οντοτήτων", + "enable-selection": "Ενεργοποίηση επιλογής οντοτήτων", + "enable-group-transfer": "Ενεργοποίηση μεταφοράς ομάδων", + "display-pagination": "Προβολή σελιδοποίησης", + "default-page-size": "Προεπιλεγμένο μέγεθος σελίδας", + "enable-assignment-actions": "Ενεργοποίηση ανάθεσης", + "enable-credentials-management": "Ενεργοποίηση διαχείρησης διαπιστευτηρίων", + "enable-login-as-user": "Ενεργοποίηση εισόδου ως χρήστη", + "enable-users-management": "Ενεργοποίηση διαχείρισης χρήστών", + "enable-customers-management": "Ενεργοποίηση διαχείρησης πελατών", + "enable-assets-management": "Ενεργοποίηση διαχείρησης assets", + "enable-devices-management": "Ενεργοποίηση διαχείρησης συσκευών", + "enable-entity-views-management": "Ενεργοποίηση διαχείρησης προβολής οντοτήτων", + "enable-dashboards-management": "Ενεργοποίηση διαχείρησης dashboard", + "open-details-on": "Άνοιγμα λεπτομερειών οντότητας σε", + "select-existing": "Ε[ιλογή υπάρχον ομάδας οντοτήτων", + "create-new": "Δημιουργία νέας ομάδας οντοτήτων", + "new-entity-group-name": "Νέο όνομα ομάδας οντοτήτων", + "entity-group-list": "Λίστα ομάδων οντοτήτων", + "entity-group-list-empty": "Δεν έχουν επιλεχθεί ομάδες οντοτήτων.", + "name-starts-with": "Το όνομα της ομάδας οντοτήτων αρχίζει από", + "entity-group-name-filter-required": "Απαιτείται φίλτρο ονόματος ομάδας οντοτήτων.", + "roles": "Ρόλοι", + "permissions": "Δικαιώματα", + "public": "Δημόσιο", + "entity-group-public": "Η ομάδα οντοτήτων είναι δημόσια", + "make-public": "Δημοσιοποίηση ομάδας οντοτήτων", + "make-private": "Ιδιωτικοποίηση ομάδας οντοτήτων", + "make-public-entity-group-title": "Είστε σίγουροι ότι θέλετε να κάνετε την ομάδα οντοτήτων '{{entityGroupName}}' δημόσια;", + "make-public-entity-group-text": "Μετά την επιβεβαίωση, η ομάδα οντοτήτων και όλες οι οντότητές της θα δημοσιοποιηθούν και θα είναι προσβάσιμες από τρίτους.", + "make-private-entity-group-title": "Είστε σίγουροι ότι θέλετε να κάνετε την ομάδα οντοτήτων '{{entityGroupName}}' ιδιωτική?", + "make-private-entity-group-text": "Μετά την επιβεβαίωση, η ομάδα οντοτήτων και όλες οι οντότητές της θα γίνουν ιδιωτικές και δεν θα είναι προσβάσιμες από τρίτους.", + "copyId": "Αντιγραφή ID ομάδας οντοτήτων", + "idCopiedMessage": "Το ID της ομάδας οντοτήτων έχει αντιγραφεί στο πρόχειρο" + }, + "entity-view": { + "entity-view": "Όψη Οντότητας", + "entity-view-required": "Απαιτείται προβολή οντότητας.", + "entity-views": "Όψεις Οντοτήτων", + "management": "Διαχείριση Όψεων Οντοτήτων", + "view-entity-views": "Προβολή οντοτήτων", + "entity-view-alias": "Ψευδώνυμο προβολής οντότητας", + "aliases": "Ψευδώνυμα προβολής οντότητας", + "no-alias-matching": "'Δεν βρέθηκαν {{alias}}'.", + "no-aliases-found": "Δεν βρέθηκαν ψευδώνυμα.", + "no-key-matching": "'Δεν βρέθηκαν {{key}}'.", + "no-keys-found": "Δεν βρέθηκαν ονόματα.", + "create-new-alias": "Δημιουργήστε ένα νέο!", + "create-new-key": "Δημιουργήστε ένα νέο!", + "duplicate-alias-error": "Βρέθηκε διπλότυπο '{{alias}}'.
Τα ψευδώνυμα προβολής οντότητας πρέπει να είναι μοναδικά εντός του dashboard.", + "configure-alias": "Ρύθμιση ψευδώνυμου '{{alias}}'", + "no-entity-views-matching": "Δεν βρέθηκαν προβολές οντοτήτων οι οποίες να αντιστοιχούν σε '{{entity}}'.", + "alias": "Ψευδώνυμο", + "alias-required": "Απαιτείται ψευδώνυμο προβολής οντοτήτων.", + "remove-alias": "Αφαίρεση ψευδωνύμου προβολής οντοτήτων", + "add-alias": "Προβολή ψευδωνύμου προβολής οντοτήτων", + "name-starts-with": "Το όνομα προβολής οντότητας αρχίζει από", + "entity-view-list": "Λίστα προβολής οντότητας", + "use-entity-view-name-filter": "Χρήση φίλτρου", + "entity-view-list-empty": "Δεν έχουν επιλεχθεί προβολές οντότητας.", + "entity-view-name-filter-required": "Απαιτείται φίλτρο ονόματος προβολής οντότητας.", + "entity-view-name-filter-no-entity-view-matched": "Δεν βρέθηκαν προβολές οντότητας οι οποίες να αρχίζουν από '{{entityView}}'.", + "add": "Προσθήκη Προβολής Οντότητας", + "assign-to-customer": "Ανάθεση σε πελάτη", + "assign-entity-view-to-customer": "Ανάθεση προβολής/ών οντότητας σε πελάτη", + "assign-entity-view-to-customer-text": "Παρακαλώ επιλέξτε τις προβολές οντότητας για να αναθέσετε σε πελάτη", + "no-entity-views-text": "Δεν βρέθηκαν προβολές οντότητας", + "assign-to-customer-text": "Παρακαλώ επιλέξτε πελάτη για να ανάθεση προβολών οντότητας", + "entity-view-details": "Λεπτομέρειες προβολής οντότητας", + "add-entity-view-text": "Προσθήκη νέας προβολής οντότητας", + "delete": "Διαγραφή προβολής οντότητας", + "assign-entity-views": "Ανάθεση προβολών οντότητας", + "assign-entity-views-text": "Ανάθεση { count, plural, 1 {1 entityView} other {# entityViews} } σε πελάτη", + "delete-entity-views": "Διαγραφή προβολών οντότητας", + "make-public": "Δημοσιοποίηση προβολής οντότητας", + "make-private": "Ιδιωτικοποίηση προβολής οντότητας", + "unassign-from-customer": "Αφαίρεση από πελάτη", + "unassign-entity-views": "Αφαίρεση προβολών οντότητας", + "unassign-entity-views-action-title": "Αφαίρεση { count, plural, 1 {1 entityView} other {# entityViews} } από πελάτη", + "assign-new-entity-view": "Ανάθεση νέας προβολής οντότητας", + "delete-entity-view-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την προβολή οντότητας '{{entityViewName}}'?", + "delete-entity-view-text": "Προσοχή, μετά την επιβεβαίωση, η προβολή της οντότητας και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-entity-views-title": "Είστε σίγουροι ότι θέλετε να προβάλετε την οντότητα { count, plural, 1 {1 entityView} other {# entityViews} };", + "delete-entity-views-action-title": "Διαγραφή { count, plural, 1 {1 entityView} other {# entityViews} }", + "delete-entity-views-text": "Προσοχή, μετά την επιβεβαίωση, όλες οι επιλεγμένες προβολές της οντότητας και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "make-public-entity-view-title": "Είστε σίγουροι ότι θέλετε να κάνετε την προβολή οντότητας '{{entityViewName}}' δημόσια?", + "make-public-entity-view-text": "Μετά την επιβεβαίωση, η προβολή της οντότητας και όλα τα δεδομένα της θα δημοσιοποιηθούν και θα είναι προσβάσιμα από τρίτους.", + "make-private-entity-view-title": "Είστε σίγουροι ότι θέλετε να κάνετε την προβολή οντότητας '{{entityViewName}}' ιδιωτική;", + "make-private-entity-view-text": "Μετά την επιβεβαίωση, η προβολή της οντότητας και όλα τα δεδομένα της θα γίνουν ιδιωτικά και θα δεν είναι προσβάσιμα από τρίτους.", + "unassign-entity-view-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε την προβολη οντότητας '{{entityViewName}}';", + "unassign-entity-view-text": "Μετά την επιβεβαίωση, η προβολή της οντότητας θα καταργηθεί και δεν θα είναι προσβάσιμη από τον πελάτη.", + "unassign-entity-view": "Αφαίρεση προβολής οντότητας", + "unassign-entity-views-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 entityView} other {# entityViews} };", + "unassign-entity-views-text": "Μετά την επιβεβαίωση, όλες οι επιλεγμένες προβολές οντοτήτων θα αφαιρεθούν και δεν θα είναι προσβάσιμες από τον πελάτη.", + "entity-view-type": "Τύπος Προβολής Οντότητας", + "entity-view-type-required": "Απαιτείται τύπος προβολής οντότητας.", + "select-entity-view-type": "Επιλογή τύπου προβολής οντότητας", + "enter-entity-view-type": "Εισαγωγή τύπου προβολής οντοτήτων", + "any-entity-view": "Οποιαδήποτε προβολή οντότητας", + "no-entity-view-types-matching": "Δεν βρέθηκαν τύποι προβολής οντότητας οι οποίοι να αντιστοιχούν με '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Δεν έχουν επιλεχθεί τύποι προβολής οντότητας.", + "entity-view-types": "Τύποι Προβολής Οντότητας", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "events": "Γεγονότα", + "details": "Λεπτομέρειες", + "copyId": "Αντιγραφή ID προβολής οντότητας", + "idCopiedMessage": "Το ID της προβολής οντότητας έχει αντιγραφεί στο πρόχειρο", + "assignedToCustomer": "Αναθέση σε πελάτη", + "unable-entity-view-device-alias-title": "Αδύνατον να διαγραφεί το ψευδώνυμο προβολής οντότητας", + "unable-entity-view-device-alias-text": "Το ψευδώνυμο συσκευής '{{entityViewAlias}}' δεν μπορεί να διαγραφεί όσο εξακωλουθεί να χρησιμοποιείτε από τα παρακάτω widget(s):
{{widgetsList}}", + "select-entity-view": "Επιλογή προβολής οντότητας", + "start-date": "Ημερομηνία έναρξης", + "start-ts": "Ώρα έναρξης", + "end-date": "Ημερομηνία λήξης", + "end-ts": "Ώρα λήξης", + "date-limits": "Όρια ημερομηνίας", + "client-attributes": "Χαρακτηριστικά Client", + "shared-attributes": "Κοινόχρηστα Χαρακτηριστικά", + "server-attributes": "Χαρακτηριστικά Server", + "timeseries": "Χρονική σειρά", + "client-attributes-placeholder": "Χαρακτηριστικά Client", + "shared-attributes-placeholder": "Κοινόχρηστα Χαρακτηριστικά", + "server-attributes-placeholder": "Χαρακτηριστικά Server", + "timeseries-placeholder": "Χρονική σειρά", + "target-entity": "Στοχευμένη οντότητα", + "attributes-propagation": "Διάδοση χαρακτηριστικών", + "attributes-propagation-hint": "Η προβολή οντοτήτων θα αντιγράφει αυτόματα καθορισμένα χαρακτηριστικά από την στοχευμένη οντότητα κάθε φορά που αποθηκεύετε ή ενημερώνετε αυτήν την προβολή οντότητας. Για λόγους απόδοσης, τα χαρακτηριστικά της στοχευμένης οντότητας δεν μεταδίδονται στην προβολή οντότητας με κάθε αλλαγή χαρακτηριστικών. Μπορείτε να ενεργοποιήσετε την αυτόματη διάδοση ρυθμίζοντας τον κόμβο \"αντιγραφή για προβολή \" στην αλυσίδα κανόνων σας και συνδέοντας τα μηνύματα \"Χαρακτηριστικά Post \" και \"Ενημερωμένα Χαρακτηριστικά \" στον νέο κόμβο.", + "timeseries-data": "Δεδομένα χρονικής σειράς", + "timeseries-data-hint": "Ρυθμίστε τα δεδομένα της χρονικής σειράς της στοχευμένης οντότητας που θα είναι διαθέσιμα στην προβολή οντοτητας. Αυτά τα δεδομένα χρονικής σειράς είναι μόνο για ανάγνωση.", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } επιλεγμένα", + "search": "Αναζήτηση προβολών οντότητας", + "select-group-to-add": "Επιλογή ομάδας για προσθήκη των επιλεγμένων προβολών οντότητας", + "select-group-to-move": "Επιλογή ομάδας για μετακίνηση των επιλεγμένων προβολών οντότητας", + "remove-entity-views-from-group": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε { count, plural, 1 {1 entity view} other {# entity views} } from group '{entityGroup}'?", + "group": "Ομάδα προβολών οντότητας", + "list-of-groups": "{ count, plural, 1 {One entity view group} other {List of # entity view groups} }", + "group-name-starts-with": "Ομάδες προβολής οντότητας των οποίων το όνομα αρχίζει από '{{prefix}}'" + }, + "event": { + "events": "Γεγονότα", + "event-type": "Τύπος Γεγονότος", + "type-error": "Σφάλμα", + "type-lc-event": "Γεγονός κύκλου ζωής", + "type-stats": "Στατιστική", + "type-debug-converter": "Αποσφαλμάτωση", + "type-debug-integration": "Αποσφαλμάτωση", + "type-debug-rule-node": "Αποσφαλμάτωση", + "type-debug-rule-chain": "Αποσφαλμάτωση", + "no-events-prompt": "Δεν βρέθηκαν γεγονότα", + "error": "Σφάλμα", + "alarm": "Alarm", + "event-time": "Ώρα Γεγονότος", + "server": "Διακομιστής", + "body": "Σώμα (body)", + "method": "Μέθοδος", + "type": "Τύπος", + "in": "Είσοδος", + "out": "Έξοδος", + "metadata": "Μεταδεδομένα", + "message": "Μήνυμα", + "entity": "Οντότητα", + "message-id": "ID Μηνύματος", + "message-type": "Τύπος Μηνύματος", + "data-type": "Τύπος Δεδομένων", + "relation-type": "Τύπος Σχέσης", + "data": "Δεδομέναα", + "event": "Γεγονός", + "status": "Κατάσταση", + "success": "Επιτυχία", + "failed": "Απέτυχε", + "messages-processed": "Επεξεργασμένα μηνύματα", + "errors-occurred": "Παρουσιάστηκαν σφάλματα" + }, + "extension": { + "extensions": "Επεκτάσεις", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } επιλέχθηκαν", + "type": "Τύπος", + "key": "Κλειδί", + "value": "Τιμή", + "id": "ID", + "extension-id": "ID επέκτασης", + "extension-type": "Τύπος επέκτασης", + "transformer-json": "JSON *", + "unique-id-required": "Το τρέχον ID επέκτασης υπάρχει ήδη.", + "delete": "Διαγραφή επέκτασης", + "add": "Προσθήκη επέκτασης", + "edit": "Επεξεργασία επέκτασης", + "view": "Προβολή επέκτασης", + "delete-extension-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την επέκταση '{{extensionId}}';", + "delete-extension-text": "Προσοχή, μετά την επιβεβαίωση, η επέκταση και όλα τα σχετικά δεδομένα θα διαγραφούν μόνιμα.", + "delete-extensions-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Προσέξτε, μετά την επιβεβαίωση θα αφαιρεθούν όλες οι επιλεγμένες επεκτάσεις.", + "converters": "Μετατροπείς", + "converter-id": "ID Μετατροπέα", + "configuration": "Διαμόρφωση", + "converter-configurations": "Ρυθμίσεις μετατροπέα", + "token": "Ετικέτα ασφαλείας", + "add-converter": "Προσθήκη μετατροπέα", + "add-config": "Προσθήκη ρυθμήσεων μετατροπέα", + "device-name-expression": "Έκφραση ονόματος συσκευής", + "device-type-expression": "Έκφραση τύπου συσκευής", + "custom": "Προσαρμοσμένο", + "to-double": "Διπλασιασμός", + "transformer": "Μετατροπέας", + "json-required": "Απαιτείται μετατροπέας JSON.", + "json-parse": "Αδύνατον να γίνει αναλύση του μετατροπέα JSON.", + "attributes": "Χαρακτηριστικά", + "add-attribute": "Προσθήκη χαρακτηριστικού", + "add-map": "Προσθήκη στοιχείου χαρτογράφισης", + "timeseries": "Χρονική σειρά", + "add-timeseries": "Προσθήκη χρονικής σειράς", + "field-required": "Απαιτείται το πεδίο", + "brokers": "Brokers", + "add-broker": "Προσθήκη broker", + "host": "Host", + "port": "Port", + "port-range": "Η Port πρέπει να είναι από 1 ως 65535.", + "ssl": "Ssl", + "credentials": "Διαπιστευτήρια", + "username": "Όνομα Χρήστη", + "password": "Κωδικός", + "retry-interval": "Διάστημα επανάληψης σε χιλιοστά του δευτερολέπτου", + "anonymous": "Ανώνυμα", + "basic": "Βασικά", + "pem": "PEM", + "ca-cert": "Αρχείο πιστοποιητικού CA *", + "private-key": "Αρχείο ιδιωτικού κλειδιού *", + "cert": "Αρχείο πιστοποιητικού *", + "no-file": "Δεν έχει επιλεχθεί αρχείο.", + "drop-file": "Αποθέστε ένα αρχείο ή κάνετε κλικ για να επιλέξετε ένα αρχείο για ανέβασμα.", + "mapping": "χαρτογράφιση", + "topic-filter": "Φίλτρο θέματος", + "converter-type": "Τύπος μετατροπέα", + "converter-json": "JSON", + "json-name-expression": "Έκφραση json ονόματος συσκευής", + "topic-name-expression": "Έκφραση θέματος ονόματος συσκευής", + "json-type-expression": "Έκφραση json τύπου συσκευής", + "topic-type-expression": "Έκφραση θέματος τύπου συσκευής", + "attribute-key-expression": "Έκφραση χαρακτηριστικού κλειδιού", + "attr-json-key-expression": "Έκφραση JSON χαρακτηριστικού κλειδιού", + "attr-topic-key-expression": "Έκφραση θέματος χαρακτηριστικού κλειδιού", + "request-id-expression": "Αίτημα έκφρασης ID", + "request-id-json-expression": "Αίτημα ID έκφρασης JSON", + "request-id-topic-expression": "Αίτημα ID έκφρασης θέματος", + "response-topic-expression": "Απάντηση έκφρασης θέματος", + "value-expression": "Έκφραση τιμής", + "topic": "Θέμα", + "timeout": "Λήξη σε χιλιοστά του δευτερολέπτου", + "converter-json-required": "Απαιτείται μετατροπέας JSON.", + "converter-json-parse": "Αδύνατον να αναλυθεί ο μετατροπέας JSON.", + "filter-expression": "Έκφραση φίλτρου", + "connect-requests": "Αιτήματα σύνδεσης", + "add-connect-request": "Προσθήκη αιτήματος σύνδεσης", + "disconnect-requests": "Αιτήματα αποσύνδεσης", + "add-disconnect-request": "Προσθήκη αιτήματος αποσύνδεσης", + "attribute-requests": "Χαρακτιριστικό αιτημάτων", + "add-attribute-request": "Προσθήκη χαρακτηριστικού αιτήματος", + "attribute-updates": "Ανανεώσεις χαρακτηριστικού", + "add-attribute-update": "Προσθήκη ανανέωσης χαρακτηριστικού", + "server-side-rpc": "Server side RPC", + "add-server-side-rpc-request": "Προσθήκη αιτήματος server-side RPC", + "device-name-filter": "Φίλτρο ονόματος συσκευής", + "attribute-filter": "ίλτρο χαρακτηριστικού", + "method-filter": "Φίλτρο μεθόδου", + "request-topic-expression": "Αίτημα έκφρασης θέματος", + "response-timeout": "Λήξη απάντησης σε χιλιοστά του δευτερολέπτου", + "topic-expression": "Έκφρασου θέματος", + "client-scope": "Πεδίο εφαρμογής πελάτη", + "add-device": "Προσθήκη συσκευής", + "opc-server": "Servers", + "opc-add-server": "Προσθήκη server", + "opc-add-server-prompt": "Παρακαλούμε προσθέστε server", + "opc-application-name": "Όνομα εφαρμογής", + "opc-application-uri": "URI Εφαρμογής", + "opc-scan-period-in-seconds": "Περίοδος σάρωσης σε δευτερόλεπτα", + "opc-security": "Ασφάλεια", + "opc-identity": "Ταυτότητα", + "opc-keystore": "Keystore", + "opc-type": "Τύπος", + "opc-keystore-type": "Τύπος", + "opc-keystore-location": "τοποθεσία *", + "opc-keystore-password": "Κωδικός", + "opc-keystore-alias": "Ψευδώνυμο", + "opc-keystore-key-password": "Κωδικός Κλειδί", + "opc-device-node-pattern": "Μοτίβο κόμβου συσκευής", + "opc-device-name-pattern": "Μοτίβο ονόματος συσκευής", + "modbus-server": "Servers/slaves", + "modbus-add-server": "Προσθήκη server/slave", + "modbus-add-server-prompt": "Παρακαλούμε προσθέστε server/slave", + "modbus-transport": "Μεταφορά", + "modbus-tcp-reconnect": "Αυτόματη επανασύνδεση", + "modbus-rtu-over-tcp": "RTU over TCP", + "modbus-port-name": "Όνομα serial port", + "modbus-encoding": "Encoding", + "modbus-parity": "Parity", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Τα Data bits πρέπει να κυμαίνονται από 7 εώς 8.", + "modbus-stopbits-range": "Τα Stop bits πρέπει να κυμαίνονται από 1 εώς 2.", + "modbus-unit-id": "ID Μονάδας", + "modbus-unit-id-range": "Το ID Μονάδας πρέπει να κυμαίνεται από 1 εώς 247.", + "modbus-device-name": "Όνομα συσκευής", + "modbus-poll-period": "Poll period (ms)", + "modbus-attributes-poll-period": "Χαρακτηριστικά poll period (ms)", + "modbus-timeseries-poll-period": "Χρονική σειρά poll period (ms)", + "modbus-poll-period-range": "Το Poll period θα πρέπει να έχει θετική τιμή.", + "modbus-tag": "Ετικέτα", + "modbus-function": "Λειτουργία", + "modbus-register-address": "Καταχώριση διεύθυνσης", + "modbus-register-address-range": "η καταχωριμένη διεύθυνση πρεπει να κυμαίνεται από 0 ως 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Το Bit index πρέπει να κυμαίνεται από 0 ως 15.", + "modbus-register-count": "Μετρητής καταχώρησης", + "modbus-register-count-range": "Ο μετρητής καταχώρησης πρεέπει να έχει θετική τιμή.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Κατάσταση", + "sync": "Σε Συγχρονισμό", + "not-sync": "Δεν Συγχρονίζεται", + "last-sync-time": "Τελευταία φορά συγχρονισμού", + "not-available": "Μη διαθέσιμο" + }, + "export-extensions-configuration": "Εξαγωγή διαμόρφωσης επεκτάσεων", + "import-extensions-configuration": "Εισαγωγή διαμόρφωσης επεκτάσεων", + "import-extensions": "Εισαγωγή επεκτάσεων", + "import-extension": "Εισαγωγή επέκτασης", + "export-extension": "Εξαγωγή επέκτασης", + "file": "Επεκτάσεις αρχείου", + "invalid-file-error": "Μη έγκυρη επέκταση αρχείου" + }, + "fullscreen": { + "expand": "Ανάπτυξη σε πλήρη οθόνη", + "exit": "Έξοδος από πλήρη οθόνη", + "toggle": "Εναλλαγή σε πλήρη οθόνη", + "fullscreen": "Πλήρης οθόνη" + }, + "function": { + "function": "Λειτουργία" + }, + "grid": { + "delete-item-title": "Είστε σίγουροι ότι θέλετε να διαγραφεί αυτό το αντικείμενο?", + "delete-item-text": "Προσοχή, μετά την επιβεβαίωση, αυτό το στοιχείο και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-items-title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Διαγραφή { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Προσοχή, μετά την επιβεβαίωση, όλα τα επιλεγμένα στοιχεία θα καταργηθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "add-item-text": "Προσθήκη νέου αντικειμένου", + "no-items-text": "Δεν βρέθηκαν αντικείμενα", + "item-details": "λεπτομέρειες αντικείμενου", + "delete-item": "Διαγραφή Αντικειμένου", + "delete-items": "Διαγραφή Αντικειμένων", + "scroll-to-top": "Κύλιση προς τα πάνω" + }, + "help": { + "goto-help-page": "Πηγαίνετε στη σελίδα" + }, + "home": { + "home": "Αρχικη", + "profile": "Προφίλ", + "logout": "Αποσυνδέση", + "menu": "Μενού", + "avatar": "Avatar", + "open-user-menu": "Άνοιγμα μενού χρήστη" + }, + "import": { + "no-file": "Δεν έχει επιλεχθεί αρχείο", + "drop-file": "Αποθέστε ένα αρχείο JSON ή κάνετε κλικ για να επιλέξετε ένα αρχείο για ανέβασμα.", + "drop-csv-file": "Αποθέστε ένα αρχείο CVS ή κάνετε κλικ για να επιλέξετε ένα αρχείο για ανέβασμα.", + "drop-file-csv": "Αποθέστε ένα αρχείο CSV ή κάνετε κλικ για να επιλέξετε ένα αρχείο για ανέβασμα.", + "column-value": "Τιμή", + "column-title": "Τίτλος", + "column-example": "Παράδειγμα δεδομένων", + "column-key": "Κλειδί χαρακτηριστικού/τηλεμετρίας", + "csv-delimiter": "Οριοθέτηση CSV", + "csv-first-line-header": "Η πρώτη σειρά περιέχει ονόματα στηλών", + "csv-update-data": "Ανανέωση χαρακτηριστικών/τηλεμετρίας", + "import-csv-number-columns-error": "Ένα αρχείο θα πρέπει να περιέχει τουλάχιστον δυο στήλες", + "import-csv-invalid-format-error": "Μη έγκυρη μορφή αρχείου. Σειρά: '{{line}}'", + "column-type": { + "name": "Όνομα", + "type": "Τύπος", + "column-type": "Τύπος στήλης", + "client-attribute": "Χαρακτηριστικό Client", + "shared-attribute": "Χαρακτηριστικό Shared", + "server-attribute": "Χαρακτηριστικό Server", + "timeseries": "Χρονική σειρά", + "entity-field": "Πεδίο Οντότητας", + "access-token": "Διακριτικό πρόσβασης" + }, + "stepper-text": { + "select-file": "Επιλογή αρχείου", + "configuration": "Εισαγωγή ρύθμισης", + "column-type": "Επιλογή τύπου στήλης", + "creat-entities": "Δημιουργία νέων οντοτήτων", + "done": "Ολοκληρώθηκε" + }, + "message": { + "create-entities": "{{count}} νέες οντότητες δημιουργήθηκαν με επιτυχία.", + "update-entities": "{{count}} οντότητες ενημερώθηκαν με επιτυχία.", + "error-entities": "Υπήρξε κάποιο σφάλμα κατά τη δημιουργία {{count}} οντοτήτων." + } + }, + "integration": { + "integration": "Ενσωμάτωση", + "integrations": "Ενσωματώσεις", + "select-integration": "Επιλογή ενσωμάτωσης", + "no-integrations-matching": "Δεν βρέθηκαν ενσωματώσεις πο να αντιστοιχούν σε '{{entity}}'.", + "integration-required": "Απαιτείται ενσωμάτωση", + "delete": "Διαγραφή ενσωμάτωσης", + "management": "Διαχείριση Ενσωματώσεων", + "add-integration-text": "Προσθήκη νέας ενσωμάτωσης", + "no-integrations-text": "Δεν βρέθηκαν ενσωματώσεις", + "selected-integrations": "{ count, plural, 1 {1 integration} other {# integrations} } επιλέχθηκαν", + "delete-integration-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την ενσωμάτωση '{{integrationName}}';", + "delete-integration-text": "Προσοχή, μετά την επιβεβαίωση, η ενσωμάτωση και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-integrations-title": "Είστε σίγουροι ότι θέλετε { count, plural, 1 {1 integration} other {# integrations} };", + "delete-integrations-action-title": "Διαγραφή { count, plural, 1 {1 integration} other {# integrations} }", + "delete-integrations-text": "Προσοχή, αφού ολοκληρωθεί η επιβεβαίωση, όλες οι επιλεγμένες ενσωματώσεις θα αφαιρεθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "events": "Γεγονότα", + "add": "Προσθήκη Ενσωμάτωσης", + "integration-details": "Λεπτομέρειες Ενσωμάτωσης", + "details": "Λεπτομέρειες", + "copyId": "Αντιγραη ID ενσωμάτωσης", + "idCopiedMessage": "Το ID της ενσωμάτωσης έχει αντιγραφεί στο πρόχειρο.", + "debug-mode": "Λειτουργία απασφαλμάτωσης", + "enable-security": "Ενεργοποίηση ασφάλειας", + "headers-filter": "Φίλτρο Headers", + "header": "Header", + "no-headers-filter": "Όχι φίλτρο headers", + "downlink-url": "URL Λήψης", + "application-uri": "URI Εφαρμογής", + "as-id": "AS ID", + "as-id-required": "Απαιτείται AS ID.", + "as-key": "AS Key", + "as-key-required": "Απαιτείται AS Key.", + "max-time-diff-in-seconds": "Μέγιστη χρονική διαφορά (δευτερόλεπτα)", + "max-time-diff-in-seconds-required": "Απαιτείται μέγιστη χρονική διαφορά.", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "base-url": "Base URL", + "base-url-required": "Απαιτείται Base URL", + "security-key": "Κλειδί ασφάλειας", + "http-endpoint": "HTTP endpoint URL", + "copy-http-endpoint-url": "Αντιγραφή HTTP endpoint URL", + "http-endpoint-url-copied-message": "Το HTTP endpoint URL έχει αντιγραφεί στο πρόχειρο", + "host": "Host", + "host-required": "Απαιτείται Host.", + "host-type": "Τύπος Host", + "host-type-required": "Απαιτείται τύπος Host.", + "custom-host": "Προσαρμοσμένο host", + "custom-host-required": "Απαιτείται προσαρμοσμένο host.", + "port": "Port", + "port-required": "Απαιτείται Port.", + "port-range": "Η Port πρέπει να κυμαίνεται από 1 ως 65535.", + "connect-timeout": "Λήξη σύνδεσης (δευτερόλεπτα)", + "connect-timeout-required": "Απαιτείται λήξη σύνδεσης.", + "connect-timeout-range": "Η λήξη σύνδεσης πρέπει να κυμαίνεται από 1 ως 200.", + "client-id": "Client ID", + "clean-session": "Καθαρή συνεδρία session", + "enable-ssl": "Ενεργοποίηση SSL", + "credentials": "Διαπιστευτήρια", + "credentials-type": "Τύπος διαπιστευτηρίων", + "credentials-type-required": "Απαιτείται τύπος διαπιστευτηρίων.", + "username": "Όνομα Χρήστη", + "username-required": "Απαιτείται όνομα χρήστη.", + "password": "Κωδικός", + "password-required": "Απαιτείται κωδικός.", + "ca-cert": "Αρχείο πιστοποιητικού CA *", + "private-key": "Αρχείο ιδιωτικού κλειδιού *", + "private-key-password": "κωδικός ιδιωτικού κλειδιού", + "cert": "Αρχείο πιστοποιητικού *", + "no-file": "Δεν έχει επιλεχθεί αρχείο.", + "drop-file": "Αποθέστε αρχείο ή κάνετε κλικ για να επιλέξετε ένα αρχείο προς ανέβασμα.", + "topic-filters": "Φίτρο θέματος", + "remove-topic-filter": "Αφαίρεση φίλτρου θέματος", + "add-topic-filter": "προσθήκη φίλτρου θέματος", + "add-topic-filter-prompt": "παρακαλούμε προσθέστε φίλτρο θέματος", + "topic": "Θέμα", + "mqtt-qos": "QoS", + "mqtt-qos-at-most-once": "Το πολύ μια", + "mqtt-qos-at-least-once": "Τουλάχιστον μια", + "mqtt-qos-exactly-once": "Ακριβώς μια", + "downlink-topic-pattern": "Μοτίβο θέματος σύνδεσης", + "downlink-topic-pattern-required": "Απαιτείται μοτίβο θέματος σύνδεσης.", + "aws-iot-endpoint": "AWS IoT Endpoint", + "aws-iot-endpoint-required": "Απαιτείται AWS IoT Endpoint.", + "aws-iot-credentials": "AWS IoT Διαπιστευτήρια", + "application-credentials": "Διαπιστευτήρια Εφαρμογής", + "api-key": "API Key", + "api-key-required": "Απαιτείται API Key.", + "auth-token": "Τεκμίριο Ταυτοποίησης", + "auth-token-required": "Απαιτείται τεκμίριο ταυτοποίησης", + "region": "Περιοχή", + "region-required": "Απαιτείται περιοχή.", + "application-id": "ID Εφαρμογής", + "application-id-required": "Απαιτείται ID εφαρμογής.", + "access-key": "Κλειδί πρόσβασης", + "access-key-required": "Απαιτείται κλειδί πρόσβασης.", + "connection-parameters": "Παράμετροι σύνδεσης", + "service-bus-namespace-name": "Service Bus Namespace Name", + "service-bus-namespace-name-required": "Απαιτείται Service Bus Namespace Name.", + "event-hub-name": "Όνομα Event Hub", + "event-hub-name-required": "Απαιτείται όνομα Event Hub.", + "sas-key-name": "Όνομα SAS Key", + "sas-key-name-required": "Απαιτείται όνομα SAS Key.", + "sas-key": "SAS Key", + "sas-key-required": "Απαιτείται SAS Key.", + "iot-hub-name": "IoT Hub Name (απαιτείται για downlink)", + "metadata": "Μεταδεδομένα", + "type": "Τύπος", + "type-required": "Απαιτείται τύπος.", + "uplink-converter": "Μετατροπέας δεδομένων uplink", + "uplink-converter-required": "Απαιτείται μετατροπέας δεδομένων uplink.", + "downlink-converter": "Μετατροπέας δεδομένων downlink", + "type-http": "HTTP", + "type-ocean-connect": "OceanConnect", + "type-sigfox": "SigFox", + "type-thingpark": "ThingPark", + "type-tmobile-iot-cdp": "T-Mobile – IoT CDP", + "type-mqtt": "MQTT", + "type-aws-iot": "AWS IoT", + "type-ibm-watson-iot": "IBM Watson IoT", + "type-ttn": "TheThingsNetwork", + "type-azure-event-hub": "Azure Event Hub", + "type-opc-ua": "OPC-UA", + "opc-ua-application-name": "Όνομα εφαρμογής", + "opc-ua-application-uri": "URI Εφαρμογής", + "opc-ua-scan-period-in-seconds": "Περίοδος σάρωσης σε δευτερόλεπτα", + "opc-ua-scan-period-in-seconds-required": "Απαιτείται περίοδος σάρωσης", + "opc-ua-timeout": "Λήξη χρόνου σε χιλιοστά του δευτερολέπτου", + "opc-ua-timeout-required": "Απαιτείται λήξη χρόνου", + "opc-ua-security": "Ασφάλεια", + "opc-ua-security-required": "Απαιτειται ασφάλεια", + "opc-ua-identity": "Ταυτότητα", + "opc-ua-identity-required": "Απαιτείται ταυτότητα", + "opc-ua-keystore": "Keystore", + "add-opc-ua-keystore-prompt": "Παρακαλούμε προσθέστε αρχείο keystore", + "opc-ua-keystore-required": "Απαιτείται keystore", + "opc-ua-type": "Τύπος", + "opc-ua-keystore-type": "Τύπος", + "opc-ua-keystore-type-required": "Απαιτέιται τύπος", + "opc-ua-keystore-location": "Τοποθεσία *", + "opc-ua-keystore-password": "Κωδικός", + "opc-ua-keystore-password-required": "Απαιτείται κωδικός", + "opc-ua-keystore-alias": "Ψευδώνυμο", + "opc-ua-keystore-alias-required": "Απαιτείται ψευδώνυμο", + "opc-ua-keystore-key-password": "Κλειδί κωδικού", + "opc-ua-keystore-key-password-required": "Απαιτείται κλειδί κωδικού", + "opc-ua-mapping": "χαρτογράφιση", + "add-opc-ua-mapping-prompt": "Παρακαλούμε προσθέστε χαρτογράφιση", + "opc-ua-mapping-type": "Τύπος χαρτογράφισης", + "opc-ua-mapping-type-required": "Απαιτείται τύπος χαρτογράφισης", + "opc-ua-device-node-pattern": "Μοτίβο Κόμβου Συσκευής", + "opc-ua-device-node-pattern-required": "Απαιτείται Μοτίβο Κόμβου Συσκευής", + "opc-ua-namespace": "Namespace", + "opc-ua-add-map": "Προσθήκη στοιχείου χαρτογράφισης", + "subscription-tags": "Ετικέτες εγγραφής", + "remove-subscription-tag": "Αφαίρεση ετικέτας εγγραφής", + "add-subscription-tag": "Προσθήκη ετικέτας εγγραφής", + "add-subscription-tag-prompt": "Παρακαλούμε προσθέστε ετικέτα εγγραφής", + "key": "Κλειδί", + "path": "Μονοπάτι", + "required": "Απαιτείται" + }, + "item": { + "selected": "Επιλέχθηκε" + }, + "js-func": { + "no-return-error": "Η λειτουργία πρέπει να επιστρέφει τιμή!", + "return-type-mismatch": "Η λειτουργία πρέπει να επιστρέφει τιμή από '{{type}}' τύπο!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Όνομα", + "value": "Τιμή", + "remove-entry": "Αφαίρεση καταχώρησης", + "add-entry": "Προσθήκη καταχωρησης", + "no-data": "Καμία καταχώρηση" + }, + "layout": { + "layout": "Διάταξη", + "manage": "Διαχείριση Διατάξεων", + "settings": "Ρυθμίσεις Διάταξης", + "color": "Χρώμα", + "main": "Κεντρικά", + "right": "Δεξιά", + "select": "Επιλογή διάταξης" + }, + "legend": { + "direction": "Κατεύθυνση Λεζάντας", + "position": "Θέση λεζάντας", + "show-max": "Προβολή μέγιστης τιμής", + "show-min": "Προβολή ελάχιστης τιμής", + "show-avg": "Προβολή μέσης τιμής", + "show-total": "Προβολή συνολικής τιμής", + "settings": "Ρυθμίσεις λεζάντας", + "min": "min", + "max": "max", + "avg": "Μ.Ο.", + "total": "Σύνολο" + }, + "login": { + "login": "Σύνδεση", + "request-password-reset": "Αίτημα επαναφοράς κωδικού πρόσβασης", + "reset-password": "Επαναφορά κωδικού πρόσβασης", + "create-password": "Δημιουργία κωδικού πρόσβασης", + "passwords-mismatch-error": "Οι καταχωρημένοι κωδικοί πρόσβασης πρέπει να είναι ίδιοι!", + "password-again": "Επανάληψη κωδικού πρόσβασης", + "sign-in": "Παρακαλώ, συνδεθείτε", + "username": "Όνομα Χρήστη (email)", + "remember-me": "Να με Θυμάσαι", + "forgot-password": "Ξεχάσατε τον κωδικό πρόσβασης;", + "password-reset": "Επαναφορά κωδικού πρόσβασης", + "new-password": "Νέος κωδικός πρόσβασης", + "new-password-again": "Επανάληψη νέου κωδικού πρόσβασης", + "password-link-sent-message": "Ο σύνδεσμος επαναφοράς κωδικού πρόσβασης στάλθηκε με επιτυχία!", + "email": "Email", + "no-account": "Δεν έχετε λογαριασμό;", + "create-account": "Δημιουργία λογαριασμού" + }, + "signup": { + "firstname": "Όνομα", + "lastname": "Επίθετο", + "email": "Email", + "signup": "Εγγραφή", + "create-password": "Δημιουργία κωδικού", + "repeat-password": "Επαναλάβετε τον κωδικό", + "have-account": "Έχετε ήδη έναν λογαριασμό;", + "signin": "Είσοδος", + "no-captcha-message": "Πρέπει να επιβεβαιώσετε ότι δεν είστε ρομπότ", + "password-length-message": "Ο κωδικός σας πρεπει να περιλαμβάνει τουλάχιστον 6 χαρακτήρες", + "email-verification": "Επιβεβαίωση email", + "email-verification-message": "Έχει σταλεί ένα email επιβεβαίωσης στην διεύθυνση email που καθορίσατε.
Παρακαλούμε ακολουθήστε τις οδηγίες που συμπεριλαμβανονται στο email ώστε να ολοκληρωθεί η εγγραφή σας.
Σημείωση: Αν δεν έχει εμφανιστεί σχετικά άμεσα το email, παρακαλούμε ελέγξτε στον φάκελο με την 'Ανεπιθύμητη Αλληλογραφία' (spam) ή ξαναπροσπαθείστε να στείλετε το email κάνοντας κλικ στο κουμπί 'Αποστολή εκ νέου'", + "account-activation-title": "Ενεργοποίηση λογαριασμού", + "account-activated": "Ο λογαριασμός ενεργοποιήθηκε με επιτυχία!", + "account-activated-text": "Συγχαρητήρια!
Ο λογαριασμός σας έχει ενεργοποιηθεί.
Τώρα μπορείται να κάνετε είσοδο στην πλατφόρμα.", + "resend": "Αποστολή εκ νέου", + "inactive-user-exists-title": "Ο ανενεργός χρήστης υπάρχει ήδη", + "inactive-user-exists-text": "Υπάρχει ήδη εγγεγραμμένος χρήστης με μη επιβεβαιωμένη διεύθυνση email.
Click Πατήστε το κουμπί 'Επαναποστολή', αν θέλετε να στείλετε ξανά email επιβεβαίωσης.", + "activating-account": "Ενεργοποίηση λογαριασμού...", + "activating-account-text": "Ο λογαριασμός σας ενεργοποιείται αυτήν τη στιγμή. Παρακαλούμε περιμένετε...", + "accept-privacy-policy": "Αποδοχή Πολιτικής Απορρήτου", + "accept": "Αποδοχή", + "privacy-policy": "Πολιτική Απορρήτου" + }, + "position": { + "top": "Κορυφή", + "bottom": "Κάτω μέρος", + "left": "Αριστερά", + "right": "Δεξιά" + }, + "profile": { + "profile": "Προφίλ", + "change-password": "Αλλαγή κωδικού", + "current-password": "Τρέχων κωδικός" + }, + "relation": { + "relations": "Σχέσεις", + "direction": "Κατεύθυνση", + "search-direction": { + "FROM": "Από", + "TO": "Σε" + }, + "direction-type": { + "FROM": "από", + "TO": "σε" + }, + "from-relations": "Εξωτερικές σχέσεις", + "to-relations": "Εσωτερικές σχέσεις", + "selected-relations": "{ count, plural, 1 {1 relation} άλλες {# relations} } επιλέχθηκαν", + "type": "Τύπος", + "to-entity-type": "Στον τύπο οντότητας", + "to-entity-name": "Στο όνομα οντότητας", + "from-entity-type": "Από τύπο οντότητας", + "from-entity-name": "Από όνομα οντότητας", + "to-entity": "Σε οντότητα", + "from-entity": "Από οντότητα", + "delete": "Διαγραφή σχέσης", + "relation-type": "Relation type", + "relation-type-required": "Απαιτείται τύπος σχέσης.", + "any-relation-type": "Οποιοσδήποτε τύπος", + "add": "Προσθήκη σχέσης", + "edit": "Επεξεργασία σχέσης", + "delete-to-relation-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε τη σχέση με την οντότητα '{{entityName}}'?", + "delete-to-relation-text": "Προσοχή, μετά την επιβεβαίωση η οντότητα '{{entityName}}' δεν θα σχετίζεται με την τρέχουσα οντότητα.", + "delete-to-relations-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 relation} άλλες {# relations} };", + "delete-to-relations-text": "Προσοχή, μετά την επιβεβαίωση όλες οι επιλεγμένες σχέσεις θα αφαιρεθούν και οι αντίστοιχες οντότητες δεν θα σχετίζονται με την τρέχουσα οντότητα.", + "delete-from-relation-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε σχέση από την οντότητα '{{entityName}}';", + "delete-from-relation-text": "Προσοχή, μετά την επιβεβαίωση η τρέχουσα οντότητα δεν θα σχετίζεται με την οντότητα '{{entityName}}'.", + "delete-from-relations-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 relation} other {# relations} };", + "delete-from-relations-text": "Προσοχή, μετά την επιβεβαίωση όλες οι επιλεγμένες σχέσεις θα αφαιρεθούν και η τρέχουσα οντότητα δεν θα σχετίζεται με τις αντίστοιχες οντότητες.", + "remove-relation-filter": "Αφαίρεση φίλτρου σχέσης", + "add-relation-filter": "Προσθήκη φίλτρου σχέσης", + "any-relation": "Οποιαδήποτε σχέση", + "relation-filters": "Φίλτρα σχέσεων", + "additional-info": "Συμπληρωματικές πληροφορίες (JSON)", + "invalid-additional-info": "Δεν είναι δυνατή η ανάλυση των πρόσθετων πληροφοριών json." + }, + "rulechain": { + "rulechain": "Αλυσίδα Κανόνων", + "rulechains": "Κανόνες", + "root": "Ρίζα", + "delete": "Διαγραφή Αλυσίδας Κανόνων", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "add": "Προσθήκη Αλυσίδας Κανόνων", + "set-root": "Δημιουργία ριζικής Αλυσίδας Κανόνων", + "set-root-rulechain-title": "Είστε σίγουροι ότι θέλετε να δημιουργήσετε τη ριζική Αλυσίδα Κανόνων '{{ruleChainName}}' ;", + "set-root-rulechain-text": "Μετά την επιβεβαίωση, η Αλυσίδα Κανόνων θα γίνει ριζική και θα χειριστεί όλα τα εισερχόμενα μηνύματα μεταφοράς.", + "delete-rulechain-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την Αλυσίδα Κανόνων '{{ruleChainName}}';", + "delete-rulechain-text": "Προσοχή, μετά την επιβεβαίωση η Αλυσίδα Κανόνων θα αφαιρεθεί και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-rulechains-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 rule chain} other {# rule chains} };", + "delete-rulechains-action-title": "Διαγραφή { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Προσοχή, μετά την επιβεβαίωση όλες οι επιλεγμένες αλυσίδες κανόνων θα καταργηθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "add-rulechain-text": "Προσθήκη νέας Αλυσίδας Κανόνων", + "no-rulechains-text": "Δεν βρέθηκαν Αλυσίδες Κανόνων", + "rulechain-details": "Λεπτομέρειες Αλυσίδας Κανόνων", + "details": "Λεπομέρειες", + "events": "Γεγονότα", + "system": "Σύστημα", + "import": "Εισαγωγή Αλυσίδας Κανόνων", + "export": "Εξαγωγή Αλυσίδας Κανόνων", + "export-failed-error": "Δεν είναι δυνατή η εξαγωγή Αλυσίδας Κανόνων: {{error}}", + "create-new-rulechain": "Δημιουργία νέας Αλυσίδας Κανόνων", + "rulechain-file": "Αρχείο Αλυσίδας Κανόνων", + "invalid-rulechain-file-error": "Δεν είναι δυνατή η εισαγωγή Αλυσίδας Κανόνων: Μη έγκυρη δομή δεδομένων Αλυσίδας Κανόνων.", + "copyId": "Αντιγραφή ταυτότητας Αλυσίδας Κανόνων", + "idCopiedMessage": "Η ταυτότητα της Αλυσίδας Κανόνων έχει αντιγραφεί στο πρόχειρο", + "select-rulechain": "Επιλογή Αλυσίδας Κανόνων", + "no-rulechains-matching": "Δεν βρέθηκαν Αλυσίδες Κανόνων που να ταιριάζουν '{{entity}}'.", + "rulechain-required": "Απαιτείται Αλυσίδα Κανόνων", + "management": "Διαχείριση κανόνων", + "debug-mode": "Λειτουργία Εκσφαλμάτωσης" + }, + "rulenode": { + "details": "Λεπτομέρειες", + "events": "Γεγονότα", + "search": "Αναζήτηση κόμβων", + "open-node-library": "Άνοιγμα βιβλιοθήκης κόμβων", + "add": "Προσθήκη κανόνα κόμβου", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "type": "Τύπος", + "description": "Περιγραφή", + "delete": "Διαγραφή κανόνα κόμβου", + "select-all-objects": "Επιλογή όλων των κόμβων και συνδέσεων", + "deselect-all-objects": "Αποεπιλογή όλων των κόμβων και συνδέσεων", + "delete-selected-objects": "Διαγραφή επιλεγμένων κόμβων και συνδέσεων", + "delete-selected": "Delete selected", + "select-all": "Select all", + "copy-selected": "Επιλογή αντιγραφής", + "deselect-all": "Αποεπιλογή όλων", + "rulenode-details": "Λεπτομέρειες κανόνα κόμβου", + "debug-mode": "Λειτουργία εντοπισμού σφαλμάτων", + "configuration": "Διαμόρφωση", + "link": "Σύνδεσμος", + "link-details": "Λεπτομέρειες συνδέσμου κανόνα κόμβου", + "add-link": "Προσθήκη συνδέσμου", + "link-label": "Ετικέτα συνδέσμου", + "link-label-required": "Απαιτείται ετικετα συνδέσμου.", + "custom-link-label": "Ετικέτα προσαρμοσμένου συνδέσμου", + "custom-link-label-required": "Απαιτείται ετικέτα προσαρμοσμένου συνδέσμου.", + "link-labels": "Ετικέτες συνδέσμου", + "link-labels-required": "Απαιτούνται ετικέτες συνδέσμου.", + "no-link-labels-found": "Δεν βρέθηκαν ετικέτες συνδέσμου", + "no-link-label-matching": "Δεν βρέθηκαν '{{label}}'.", + "create-new-link-label": "Δημιουργήστε μια νέα!", + "type-filter": "Φίλτρο", + "type-filter-details": "Φιλτράρετε εισερχόμενα μηνύματα με διαμορφωμένες συνθήκες", + "type-enrichment": "Εμπλουτισμός", + "type-enrichment-details": "Προσθέστε επιπλέον πληροφορίες στα Μεταδεδομένα του μηνύματος", + "type-transformation": "Μεταμόρφωση", + "type-transformation-details": "Αλλαγή ωφέλιμου φορτίου και μεταδεδομένων μηνύματος", + "type-action": "Ενέργεια", + "type-action-details": "Εκτέλεση ειδικής ενέργειας", + "type-analytics": "Analytics", + "type-analytics-details": "Εκτελέστε ανάλυση δεδομένων που διαβιβάζονται με ροή ή συνεχίζονται", + "type-external": "Εξωτερικός", + "type-external-details": "Αλληλεπίδραση με εξωτερικό σύστημα", + "type-rule-chain": "Αλυσίδα κανόνων", + "type-rule-chain-details": "Προωθεί τα εισερχόμενα μηνύματα σε συγκεκριμένη αλυσίδα κανόνων", + "type-input": "Εισαγωγήγή", + "type-input-details": "Λογική εισαγωγή της αλυσίδας κανόνων, προωθεί τα εισερχόμενα μηνύματα στον επόμενο σχετικό κανόνα κόμβου", + "type-unknown": "Άγνωστο", + "type-unknown-details": "Ανεπεξέργαστος κανόνας κόμβου", + "directive-is-not-loaded": "Δεν είναι διαθέσιμη καθορισμένη οδηγία διαμόρφωσης '{{directiveName}}'.", + "ui-resources-load-error": "Αποτυχία φόρτωσης διαμόρφωσης πόρων ui.", + "invalid-target-rulechain": "Δεν είναι δυνατή η επίλυση της αλυσίδας κανόνα στόχου!", + "test-script-function": "Λειτουργία σεναρίου δοκιμής", + "message": "Μήνυμα", + "message-type": "Τύπος μηνύματος", + "select-message-type": "Επιλογή τύπου μηνύματος", + "message-type-required": "Απαιτείται τύπος μηνύματος", + "metadata": "Μεταδεδομένα", + "metadata-required": "Οι καταχωρίσεις μεταδεδομένων δεν μπορούν να είναι κενές.", + "output": "Απόδοση", + "test": "Τεστ", + "help": "Βοήθεια", + "reset-debug-mode": "Επαναφορά λειτουργίας εντοπισμού σφαλμάτων σε όλους τους κόμβους" + }, + "role": { + "role": "Ρόλος", + "roles": "Ρόλοι", + "management": "Διαχείριση ρόλων", + "view-roles": "Προβολή ρόλων", + "no-roles-matching": "Δεν βρέθηκαν ρόλοι που να ταιριάζουν '{{entity}}'.", + "role-list": "Λίστα ρόλων", + "add": "Προσθήκη ρόλου", + "view": "Προβολή ρόλου", + "no-roles-text": "Δεν βρέθηκαν ρόλοι", + "role-details": "Λεπτομέρειες ρόλου", + "add-role-text": "Προσθήκη νέου ρόλου", + "delete": "Διαγραφή ρόλου", + "delete-roles": "Διαγραφή ρόλων", + "delete-role-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε το ρόλο '{{roleName}}';", + "delete-role-text": "Προσοχή, μετά την επιβεβαίωση ο ρόλος και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-roles-title": "Είστε σίγουροι ότι θέλετε να παίξετε { count, plural, 1 {1 role} other {# roles} };", + "delete-roles-action-title": "Διαγραφή { count, plural, 1 {1 role} other {# roles} }", + "delete-roles-text": "Προσοχή, μετά την επιβεβαίωση όλοι οι επιλεγμένοι ρόλοι θα καταργηθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "role-type": "΄Τύπος ρόλου", + "role-type-required": "Απαιτείται τύπος ρόλου.", + "select-role-type": "Επιλογή τύπου ρόλου", + "enter-role-type": "Εισαγωγή τύπου ρόλου", + "any-role": "Οποιοσδήποτε ρόλος", + "no-role-types-matching": "Δεν βρέθηκαν τύποι ρόλων που να ταιριάζουν '{{entitySubtype}}'.", + "role-type-list-empty": "Δεν έγινε επιλογή τύπου ρόλου.", + "role-types": "Τύποι ρόλου", + "name": "Όνομα", + "name-required": "Απαιτείται όνομα.", + "description": "Περιγραφή", + "events": "Γεγονότα", + "details": "Λεπτομέρειες", + "copyId": "Αντιγραφή ταυτότητας ρόλου", + "idCopiedMessage": "Η ταυτότητα ρόλου έχει αντιγραφεί στο πρόχειρο", + "permissions": "Άδειες", + "role-required": "Απαιτείται ρόλος", + "display-type": { + "GENERIC": "Γενικός", + "GROUP": "Ομάδα" + } + }, + "group-permission": { + "user-group-roles": "Ρόλοι ομάδας χρηστών", + "entity-group-permissions": "Άδειες ομάδας Οντοτήτων", + "role-type": "Τύπος ρόλου", + "role-name": "Όνομα ρόλου", + "group-type": "Τύπος ομάδας", + "group-name": "Όνομα ομάδας", + "group-owner": "Κάτοχος ομάδας", + "user-group-name": "Όνομα χρήστη ομάδας", + "user-group-owner": "Κάτοχος χρήστη ομάδας", + "edit": "Επεξεργασία Αδειών", + "delete": "Διαγραφή αδειών", + "selected-group-permissions": "{ count, plural, 1 {1 group permission} other {# group permissions} } επιλέχθηκαν", + "delete-group-permission-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε την άδεια ομάδας '{{roleName}}';", + "delete-group-permission-text": "Προσοχή, μετά την επιβεβαίωση η άδεια ομάδας και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-group-permission": "Διαγραφή άδειας ομάδας", + "delete-group-permissions-title": "Έίστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 group permission} other {# group permission} };", + "delete-group-permissions-text": "Προσοχή, μετά την επιβεβαίωση όλες οι επιλεγμένες άδειες ομάδας θα καταργηθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-group-permissions": "Διαγραφή αδειών ομάδας", + "add-group-permission": "Προσθήκη ομαδικής άδειας", + "edit-group-permission": "Επεξεργασία άδειας ομάδας", + "entity-group": "Ομάδα οντοτήτων", + "user-group": "Χρήστης ομάδας", + "no-owners-matching": "Δεν βρέθηκαν κάτοχοι που να ταιριάζουν '{{owner}}'.", + "target-owner-required": " Απαιτείται κάτοχος της ομάδας οντότητας.", + "target-user-group-owner-required": "Απαιτείται κάτοχος χρήστη ομάδας." + }, + "permission": { + "permissions-required": "Τουλάχιστον μία εγγραφή άδειας πρέπει να οριστεί.", + "remove-permission": "Καταργήστε την καταχώριση δικαιωμάτων", + "add-permission": "Προσθήκη καταχώρησης άδειας", + "resource": { + "resource": "Πηγή", + "select-resource": "Επιλογή πηγής", + "resource-required": "Απαιτείται πηγή", + "no-resources-matching": "Δεν βρέθηκαν πηγές που να ταιριάζουν '{{resource}}'.", + "display-type": { + "ALL": "Όλες", + "PROFILE": "Προφίλ", + "ADMIN_SETTINGS": "Ρυθμίσεις Διαχειριστή", + "ALARM": "Alarm", + "DEVICE": "Συσκευή", + "ASSET": "Asset", + "CUSTOMER": "Πελάτης", + "DASHBOARD": "Dashboard", + "ENTITY_VIEW": "Προβολή οντοτήτων", + "TENANT": "Μισθωτής", + "RULE_CHAIN": "Αλυσίδα Κανόνα", + "USER": "Χρήστης", + "WIDGETS_BUNDLE": "Δέσμη Widgets", + "WIDGET_TYPE": "Τύπος Widget", + "CONVERTER": "Μετατροπέας", + "INTEGRATION": "Ενσωμάτωση", + "SCHEDULER_EVENT": "Πρόγραμμα εκδηλώσεων", + "BLOB_ENTITY": "Δέσμη Οντότητας", + "CUSTOMER_GROUP": "Ομάδα Πελατών", + "DEVICE_GROUP": "Ομάδα Συσκευών", + "ASSET_GROUP": "Ομάδες Asset", + "USER_GROUP": "Ομάδα Χρηστών", + "ENTITY_VIEW_GROUP": "Ομάδα Οντοτήτων", + "DASHBOARD_GROUP": "Ομάδα Dashboard", + "ROLE": "Ρόλος", + "GROUP_PERMISSION": "Ομαδική Άδεια", + "WHITE_LABELING": "Εμφάνιση", + "AUDIT_LOG": "Αρχείο Ελέγχου" + } + }, + "operation": { + "operation": "Εργασία", + "operations": "Εργασίες", + "operations-required": "Τουλάχιστον μία εργασία πρέπει να καθοριστεί.", + "enter-operation": "Εισαγωγή εργασίας", + "no-operations-matching": "Δεν βρέθηκαν εργασίες που να ταιριάζουν '{{operation}}'.", + "display-type": { + "ALL": "'Ολες", + "CREATE": "Δημιουργία", + "READ": "Ανάγνωση", + "WRITE": "Εγγραφή", + "DELETE": "Διαγραφή", + "ASSIGN_TO_CUSTOMER": "Ανάθεση σε Πελάτη", + "UNASSIGN_FROM_CUSTOMER": "Αποσύνδεση από Πελάτη", + "RPC_CALL": "Κλήση RPC", + "READ_CREDENTIALS": "Ανάγνωση Διαπιστευτηρίων", + "WRITE_CREDENTIALS": "Γράψτε Διαπιστευτήρια", + "READ_ATTRIBUTES": "Ανάγνωση Χαρακτηριστικών", + "WRITE_ATTRIBUTES": "Γράψτε Χαρακτηριστικά", + "READ_TELEMETRY": "Ανάγνωση Τηλεμετρίας", + "WRITE_TELEMETRY": "Γράψτε Τηλεμετρία", + "CLAIM_DEVICES": "Αιτήματα Συσκευών", + "IMPERSONATE": "Impersonate", + "CHANGE_OWNER": "Αλλαγή Κατόχου", + "ADD_TO_GROUP": "Προσθήκη στην Ομάδα", + "REMOVE_FROM_GROUP": "Αφαίρεση από την Ομάδα" + } + } + }, + "scheduler": { + "scheduler": "Προγραμματιστής", + "scheduler-event": "Προγραμματισμένο γεγονός", + "select-scheduler-event": "Επιλέξτε προγραμματισμένα γεγονότα που να ταιριάζουν '{{entity}}'", + "scheduler-event-required": "Απαιτείται προγραμματισμένο γεγονός", + "management": "Διαχείριση Προγράμματος", + "scheduler-events": "Προγραμματισμένα γεγονότα", + "add-scheduler-event": "Προσθήκη προγραμματισμένου γεγονότος", + "search-scheduler-events": "Αναζήτηση προγραμματισμένων γεγονότων", + "created-time": "Χρόνος που δημιουργήθηκε", + "name": "Όνομα", + "type": "Τύπος", + "created_customer": "Πελάτης που δημιουργήθηκε", + "edit-scheduler-event": "Επεξεργασία προγραμματισμένου γεγονότος", + "view-scheduler-event": "Προβολή προγραμματισμένου γεγονότος", + "delete-scheduler-event": "Διαγραφή προγραμματισμένου γεγονότος", + "no-scheduler-events": "Δεν βρέθηκαν προγραμματισμένα γεγονότα", + "selected-scheduler-events": "{ count, plural, 1 {1 scheduler event} other {# scheduler events} } επιλέχθηκαν", + "delete-scheduler-event-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε το προγραμματισμένο γεγονός '{{schedulerEventName}}';", + "delete-scheduler-event-text": "Προσοχή, μετά την επιβεβαίωση το προγραμματισμένο γεγονός και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-scheduler-events-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 scheduler event} other {# scheduler events} };", + "delete-scheduler-events-text": "Προσοχή, μετά την επιβεβαίωση όλα τα επιλεγμένα προγραμματισμένα γεγονότα θα καταργηθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "create": "Δημιουργία προγραμματισμένου γεγονότος", + "edit": "Επεξεργασία προγραμματισμένου γεγονότος", + "view": "Προβολή προγραμματισμένου γεγονότος", + "name-required": "Απαιτείται Όνομα", + "configuration": "Διαμόρφωση", + "schedule": "Πρόγραμμα", + "start": "Έναρξη", + "date": "Ημερομηνία", + "time": "Ώρα", + "repeat": "Επανάληψη", + "repeats": "Επαναλήψεις", + "daily": "Καθημερινά", + "weekly": "Εβδομαδιαία", + "timer": "Χρονομετρητής", + "repeats-required": "Απαιτούνται επαναλήψεις.", + "repeat-on": "Επανάληψη σε", + "repeat-every": "Επανάληψη κάθε", + "ends-on": "Τελειώνει σε", + "sunday-label": "K", + "monday-label": "Δ", + "tuesday-label": "T", + "wednesday-label": "Τ", + "thursday-label": "Π", + "friday-label": "Π", + "saturday-label": "Σ", + "repeat-on-sunday": "Επανάληψη την Κυριακή", + "repeat-on-monday": "Επανάληψη τη Δευτέρα", + "repeat-on-tuesday": "Επανάληψη την Τρίτη", + "repeat-on-wednesday": "Επανάληψη την Τετάρτη", + "repeat-on-thursday": "Επανάληψη την Πέμπτη", + "repeat-on-friday": "Επανάληψη την Παρασκευή", + "repeat-on-saturday": "Επανάληψη το Σάββατο", + "event-type": "Τύπος Γεγονότος", + "select-event-type": "Επιλογή Τύπου Γεγονότος", + "event-type-required": "Απαιτείται Τύπος Γεγονότος.", + "list-mode": "Προβολή Λίστας", + "calendar-mode": "Προβολή Ημερολογίου", + "calendar-view-type": "Προβολή τύπου ημερολογίου", + "month": "Μήνας", + "week": "Εβδομάδα", + "day": "Ημέρα", + "agenda-week": "Εβδομαδιαία Ατζέντα", + "agenda-day": "Ημερήσια Ατζέντα", + "list-year": "Λίστα Έτους", + "list-month": "Λίστα Μήνα", + "list-week": "Λίστα Εβδομάδος", + "list-day": "Λίστα Ημέρας", + "today": "Σήμερα", + "navigate-before": "Πλοηγηθείτε Πριν", + "navigate-next": "Πλοηγηθείτε Μετά", + "starting-from": "Έναρξη Από", + "until": "μέχρι", + "on": "σε", + "sunday": "Κυριακή", + "monday": "Δευτέρα", + "tuesday": "Τρίτη", + "wednesday": "Τετάρτη", + "thursday": "Πέμπτη", + "friday": "Παρασκευή", + "saturday": "Σάββατο", + "originator": "Δημιουργός" , + "single-entity": "Ενιαία Οντότητα", + "group-of-entities": "Ομάδα Οντοτήτων", + "single-device": "Ενιαία Συσκευή", + "group-of-devices": "Ομάδα Συσκευών", + "message-body": "Σώμα μηνυμάτων", + "target": "Στόχος", + "rpc-method": "Μέθοδος", + "rpc-method-required": "Απαιτείται μέθοδος", + "rpc-params": "Παράμετροι", + "select-dashboard-state": "Επιλογή κατάστασης πίνακα ελέγχου", + "hours": "Ώρες", + "minutes": "Λεπτά", + "seconds": "Δευτερόλεπτα", + "time-interval-required": "Απαιτείται χρονικό διάστημα", + "time-unit-required": "Απαιτείται μονάδα ώρας" + }, + "report": { + "report-config": "Διαμόρφωση αναφοράς", + "email-config": "Διαμόρφωση email", + "dashboard-state-param": "Τιμή παραμέτρου κατάστασης πίνακα", + "base-url": "Βάση URL", + "base-url-required": "Απαιτείται Βάση URL.", + "use-dashboard-timewindow": "Χρησιμοποιήστε το χρονικό πλαίσιο του πίνακα ελέγχου", + "timewindow": "Χρονικό πλαίσιο", + "name-pattern": "Αναφορά πρότυπου ονόματος", + "name-pattern-required": "Απαιτείται αναφορά πρότυπου ονόματος", + "type": "Τύπος αναφοράς", + "use-current-user-credentials": "Χρησιμοποιήστε τα τρέχοντα διαπιστευτήρια του χρήστη", + "customer-user-credentials": "Διαπιστευτήρια του χρήστη του πελάτη", + "customer-user-credentials-required": "Απαιτούνται διαπιστευτήρια του χρήστη του πελάτη", + "generate-test-report": "Δημιουργία αναφοράς δοκιμής", + "send-email": "Αποστολή email", + "from": "Από", + "from-required": "Απαιτείται Από.", + "to": "Σε", + "to-required": "Απαιτείται Σε.", + "cc": "Cc", + "bcc": "Bcc", + "subject": "Θέμα", + "subject-required": "Απαιτείται Θέμα.", + "body": "Σώμα", + "body-required": "Απαιτείται Σώμα." + }, + "blob-entity": { + "blob-entity": "Ογκώδης οντότητα", + "select-blob-entity": "Επιλογή ογκώδους οντότητας", + "no-blob-entities-matching": "Δεν βρέθηκαν ογκώδεις οντότητες που να ταιριάζουν '{{entity}}'.", + "blob-entity-required": "Απαιτείται ογκώδης οντότητα", + "files": "Αρχεία", + "search": "Αναζήτηση αρχείων", + "clear-search": "Εκκαθάριση αναζήτησης", + "no-blob-entities-prompt": "Δεν βρέθηκαν αρχεία", + "report": "Αναφορά", + "created-time": "Χρόνος που δημιουργήθηκε", + "name": "Όνομα", + "type": "Τύπος", + "created_customer": "Δημιουργήθηκε από Πελάτη", + "download-blob-entity": "Λήψη αρχείου", + "delete-blob-entity": "Διαγραφή αρχείου", + "delete-blob-entity-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε το αρχείο '{{blobEntityName}}';", + "delete-blob-entity-text": "Προσοχή, μετά την επιβεβαίωση τα δεδομένα αρχείου θα διαγραφούν οριστικά." + }, + "timezone": { + "timezone": "Ζώνη Ώρας", + "select-timezone": "Επιλογή Ζώνης Ώρας", + "no-timezones-matching": "Δεν βρέθηκαν Ζώνες ώρας που να ταιριάζουν'{{timezone}}' .", + "timezone-required": "Απαιτείται Ζώνη Ώρας." + }, + "tenant": { + "tenant": "Μισθωτής", + "tenants": "Μισθωτές", + "management": "Διαχείριση Μισθωτών", + "add": "Πρόσθεση Μισθωτή", + "admins": "Διαχειριστές", + "manage-tenant-admins": "Επεξεργασία των διαχειριστών του Μισθωτή", + "delete": "Διαγραφή Μισθωτή", + "add-tenant-text": "Πρόσθεση νέου Μισθωτή", + "no-tenants-text": "Δεν βρέθηκαν Μισθωτές", + "tenant-details": "Λεπτομέρειες Μισθωτή", + "delete-tenant-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε τον Μισθωτή '{{tenantTitle}}';", + "delete-tenant-text": "Προσοχή, μετά την επιβεβαίωση ο Μισθωτής και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-tenants-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 tenant} other {# tenants} };", + "delete-tenants-action-title": "Διαγραφή { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Προσοχή, μετά την επιβεβαίωση όλοι οι επιλεγμένοι Μισθωτές θα αφαιρεθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "title": "Τίτλος", + "title-required": "Απαιτείται Τίτλος.", + "description": "Περιγραφή", + "details": "Λεπτομέρειες", + "events": "Γεγονότα", + "copyId": "Αντιγραφή Ταυτότητας του Μισθωτή", + "idCopiedMessage": "Η Ταυτότητα του Μισθωτή έχει αντιγραφεί στο πρόχειρο", + "select-tenant": "Επιλογή Μισθωτή", + "no-tenants-matching": "Δεν βρέθηκαν Μισθωτές που να ταιριάζουν '{{entity}}'.", + "tenant-required": "Απαιτείται Μισθωτής", + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } επιλέχθηκαν", + "search": "Αναζήτηση Μισθωτών", + "allow-white-labeling": "Επιτρέπεται Προσαρμογή Εμφάνισης", + "allow-customer-white-labeling": "Επιτρέπεται Προσαρμογή Εμφάνισης Πελάτη" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Ημέρες", + "hours": "Ώρες", + "minutes": "Λεπτά", + "seconds": "Δευτερόλεπτα", + "advanced": "Προηγμένος" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "Πραγματικός Χρόνος", + "history": "Ιστορικό", + "last-prefix": "Τελευταίος", + "period": "από {{ startTime }} σε {{ endTime }}", + "edit": "Επεξεργασία Χρονικού Πλαισίου", + "date-range": "Εύρος ημερομηνιών", + "last": "Τελευταίος", + "time-period": "Χρονική Περίοδος" + }, + "user": { + "user": "Χρήστης", + "users": "Χρήστες", + "management": "Διαχείριση Χρηστών", + "customer-users": "Χρήστες του Πελάτη", + "tenant-admins": "Διαχειριστές Μισθωτή", + "sys-admin": "Διαχειριστής Συστήματος", + "tenant-admin": "Διαχειριστής Μισθωτή", + "customer": "Πελάτης", + "anonymous": "Ανώνυμος", + "add": "Προσθήκη χρήστη", + "delete": "Διαγραφή χρήστη", + "add-user-text": "Προσθήκη νέου Χρήστη", + "no-users-text": "Δεν βρέθηκαν Χρήστες", + "user-details": "Λεπτομέρειες Χρήστη", + "delete-users": "Διαγραφή Χρηστών", + "delete-user-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε το Χρήστη '{{userEmail}}'?", + "delete-user-text": "Προσοχή, μετά την επιβεβαίωση ο Χρήστης και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-users-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 user} other {# users} };", + "delete-users-action-title": "Διαγραφή { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Προσοχή, μετά την επιβεβαίωση όλοι οι επιλεγμένοι Χρήστες θα αφαιρεθούν και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "activation-email-sent-message": "Το email ενεργοποίησης στάλθηκε με επιτυχία!", + "resend-activation": "Επανάληψη ενεργοποίησης", + "email": "Email", + "email-required": "Απαιτείται email.", + "invalid-email-format": "Μη έγκυρη μορφή email.", + "first-name": "Όνομα", + "last-name": "Επίθετο", + "description": "Περιγραφή", + "default-dashboard": "Προκαθορισμένος πίνακας ελέγχου", + "always-fullscreen": "Πάντα με πλήρη οθόνη", + "select-user": "Επιλογή Χρήστη", + "no-users-matching": "Δεν βρέθηκαν χρήστες που να ταιριάζουν '{{entity}}'.", + "user-required": "Απαιτείται Χρήστης", + "activation-method": "Μέθοδος ενεργοποίησης", + "display-activation-link": "Εμφάνιση συνδέσμου ενεργοποίησης", + "send-activation-mail": "Αποστολή mail ενεργοποίησης", + "activation-link": "Σύνδεσμος ενεργοποίησης Χρήστη", + "activation-link-text": "΄Προκειμένου να ενεργοποιήσετε το Χρήστη, χρησιμοποιήστε το εξής activation link :", + "copy-activation-link": "Αντιγραφή συνδέσμου ενεργοποίησης", + "activation-link-copied-message": "Ο σύνδεσμος ενεργοποίησης χρήστη έχει αντιγραφεί στο πρόχειρο", + "selected-users": "{ count, plural, 1 {1 user} other {# users} } επιλέχθηκαν", + "search": "Αναζήτηση Χρηστών", + "details": "Λεπτομέρειες", + "login-as-tenant-admin": "Συνδεθείτε ως Διαχειριστής Μισθωτή", + "login-as-customer-user": "Συνδεθείτε ως Χρήστης του Πελάτη", + "select-group-to-add": "Επιλέξτε την ομάδα προορισμού για να προσθέσετε επιλεγμένους Χρήστες", + "select-group-to-move": "Επιλέξτε την ομάδα προορισμού για να μετακινήσετε επιλεγμένους χρήστες", + "remove-users-from-group": "Είστε σίγουροι ότι θέλετε να καταργήσετε { count, plural, 1 {1 user} other {# users} } από την ομάδα '{entityGroup}';", + "group": "Ομάδα από Χρήστες", + "list-of-groups": "{ count, plural, 1 {One user group} other {List of # user groups} }", + "group-name-starts-with": "Ομάδες Χρηστών των οποίων τα ονόματα ξεκινούν με'{{prefix}}'" + }, + "value": { + "type": "Είδος τιμής", + "string": "Συμβολοσειρά", + "string-value": "Τιμή συμβολοσειράς", + "integer": "Ακέραιος", + "integer-value": "Ακέραια τιμή", + "invalid-integer-value": "Μη έγκυρη ακέραια τιμή", + "double": "Πραγματικός", + "double-value": "Πραγματική τιμή", + "boolean": "Λογικό", + "boolean-value": "Λογική τιμή", + "false": "Εσφαλμένος", + "true": "Αληθής", + "long": "Μακρύς" + }, + "widget": { + "widget-library": "Βιβλιοθήκη Widget", + "widget-bundle": "Δέσμη Widget", + "select-widgets-bundle": "Επιλογή δέσμης Widgets", + "management": "Διαχείριση Widget", + "editor": "Συντάκτης Widget", + "widget-type-not-found": "Πρόβλημα φόρτωσης διαμόρφωσης Widget.
Probably associated\n widget type was removed.", + "widget-type-load-error": "Το Widget δεν φορτώθηκε λόγω των παρακάτω σφαλμάτων:", + "remove": "Αφαίρεση Widget", + "edit": "Επεξεργασία Widget", + "remove-widget-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε το Widget '{{widgetTitle}}'?", + "remove-widget-text": "Προσοχή, μετά την επιβεβαίωση το widget και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "timeseries": "Χρονική σειρά", + "search-data": "Αναζήτηση δεδομένων", + "no-data-found": "Δεν βρέθηκαν δεδομένα", + "latest-values": "Τελευταίες αξίες", + "rpc": "Έλεγχος Widget", + "alarm": "Alarm widget", + "static": "Στατικό widget", + "select-widget-type": "Επιλογή τύπου Widget", + "missing-widget-title-error": "Ο τίτλος Widget πρέπει να καθοριστεί!", + "widget-saved": "Το Widget αποθηκεύτηκε", + "unable-to-save-widget-error": "Δεν είναι δυνατή η αποθήκευση του Widget! Το Widget έχει σφάλματα!", + "save": "Αποθήκευση widget", + "saveAs": "Αποθήκευση widget ως", + "save-widget-type-as": "Αποθήκευση τύπου Widget type ως", + "save-widget-type-as-text": "Παρακαλούμε εισάγετε νέο τίτλο Widget και/ή επιλέξετε στοχευμένη δέσμη widgets", + "toggle-fullscreen": "Λειτουργεία πλήρους οθόνης", + "run": "Εκτέλεση Widget", + "title": "Τίτλος Widget", + "title-required": "Απαιτείται τίτλος Widget.", + "type": "Τύπος Widget", + "resources": "Πόροι", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Αφαίρεση πηγής", + "add-resource": "Προσθήκη πηγής", + "html": "HTML", + "tidy": "Τακτοποιημένος", + "css": "CSS", + "settings-schema": "Ρυθμίσεις σχήματος", + "datakey-settings-schema": "Πλήκτρο δεδομένων σχήματος ρυθμίσεων", + "javascript": "Javascript", + "remove-widget-type-title": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε τον τύπο Widget '{{widgetName}}'?", + "remove-widget-type-text": "Προσοχή, μετά την επιβεβαίωση ο τύπος Widget και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "remove-widget-type": "Αφαίρεση τύπου Widget", + "add-widget-type": "Προσθήκη νέου τύπου Widget", + "widget-type-load-failed-error": "Αποτυχία φόρτωσης τύπου Widget!", + "widget-template-load-failed-error": "Αποτυχία φόρτωσης προτύπου Widget!", + "add": "Προσθήκη Widget", + "undo": "Αναίρεση αλλαγών Widget", + "export": "Εξαγωγή Widget", + "export-data": "Εξαγωγή δεδομένων Widget", + "export-to-csv": "Εξαγωγή δεδομένων σε CSV...", + "export-to-excel": "Εξαγωγή δεδομένων σε XLS...", + "no-data": "Δεν υπάρχουν δεδομένα για εμφάνιση στο Widget" + }, + "widget-action": { + "header-button": "Κουμπί κεφαλίδας στη νέα κατάσταση του πίνακα ελέγχου", + "update-dashboard-state": "Ενημέρωση της τρέχουσας κατάστασης του dashboard", + "open-dashboard": "Πλοήγηση σε άλλο dashboard", + "custom": "Προσαρμοσμένη ενέργεια", + "target-dashboard-state": "Κατάσταση προορισμού dashboard", + "target-dashboard-state-required": "Απαιτείται κατάσταση προορισμού dashboard", + "set-entity-from-widget": "Ορισμός οντότητας από Widget", + "target-dashboard": "dashboard προορισμού", + "open-right-layout": "Ανοίξτε τη δεξιά διάταξη του dashboard (προβολή κινητού)" + }, + "widgets-bundle": { + "current": "Τρέχουσα δέσμη", + "widgets-bundles": "Δέσμες Widgets", + "add": "Προσθήκη δέσμης Widgets", + "delete": "Διαγραφή δέσμης Widgets", + "title": "Τίτλος", + "title-required": "Απαιτείται τίτλος.", + "add-widgets-bundle-text": "Προσθήκη νέας δέσμης widgets", + "no-widgets-bundles-text": "Δεν βρέθηκαν δέσμες widgets", + "empty": "Η δέσμη Widgets είναι κενή", + "details": "Λεπτομέρειες", + "widgets-bundle-details": "Λεπτομέρειες δέσμης Widgets", + "delete-widgets-bundle-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε τη δέσμη Widgets '{{widgetsBundleTitle}}';", + "delete-widgets-bundle-text": "Προσοχή, μετά την επιβεβαίωση η δέσμη Widget και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "delete-widgets-bundles-title": "Είστε σίγουροι ότι θέλετε να διαγράψετε { count, plural, 1 {1 widgets bundle} other {# widgets bundles} };", + "delete-widgets-bundles-action-title": "Διαγραφή { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Προσοχή, μετά την επιβεβαίωση όλες οι επιλεγμένες δέσμες Widget και όλα τα σχετικά δεδομένα θα διαγραφούν οριστικά.", + "no-widgets-bundles-matching": "Δεν βρέθηκαν δέσμες Widgets που να ταιριάζουν'{{widgetsBundle}}'.", + "widgets-bundle-required": "Απαιτείται δέσμη Widgets.", + "system": "Σύστημα", + "import": "Εισαγωγή δέσμης Widgets", + "export": "Εξαγωγή δέσμης Widgets", + "export-failed-error": "Δεν είναι δυνατή η εξαγωγή δέσμης Widgets: {{error}}", + "create-new-widgets-bundle": "Δημιουργία νέας δέσμης Widgets", + "widgets-bundle-file": "Αρχείο δέσμης Widgets", + "invalid-widgets-bundle-file-error": "Δεν είναι δυνατή η εισαγωγή δέσμης Widgets: Μη έγκυρη δομή δεδομένων Widgets." + }, + "widget-config": { + "data": "Δεδομένα", + "settings": "Ρυθμίσεις", + "advanced": "Προηγμένος", + "title": "Τίτλος", + "general-settings": "Γενικές ρυθμίσεις", + "display-title": "Εμφάνιση τίτλου", + "drop-shadow": "Σκίαση", + "enable-fullscreen": "Ενεργοποίηση πλήρους οθόνης", + "enable-data-export": "Ενεργοποίηση εξαγωγής δεδομένων", + "background-color": "Χρώμα φόντου", + "text-color": "Χρώμα κειμένου", + "padding": "Εσωτερικό περιθώριο", + "margin": "Περιθώριο", + "widget-style": "Στυλ Widget", + "title-style": "Στυλ τίτλου", + "mobile-mode-settings": "Ρυθμίσεις λειτουργίας κινητού", + "order": "Εντολή", + "height": "Ύψος", + "units": "Ειδικό σύμβολο για εμφάνιση δίπλα στην αξία", + "decimals": "Αριθμός ψηφίων μετά το κυμαινόμενο σημείο", + "timewindow": "Timewindow", + "use-dashboard-timewindow": "Χρήση dashboard timewindow", + "display-timewindow": "Απεικόνιση timewindow", + "display-legend": "Απεικόνιση λεζάντας", + "datasources": "Πηγές δεδομένων", + "maximum-datasources": "Το μέγιστο { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "Τύπος", + "datasource-parameters": "Παράμετροι", + "remove-datasource": "Κατάργηση της πηγής δεδομένων", + "add-datasource": "Προσθήκη πηγής δεδομένων", + "target-device": "Target device", + "alarm-source": "Πηγή Alarm", + "actions": "Ενέργειες", + "action": "ενέργεια", + "add-action": "Προσθήκη ενέργειας", + "search-actions": "Αναζήτηση ενεργειών", + "action-source": "Πηγή ενέργειας", + "action-source-required": "Απαιτείται πηγή ενέργειας.", + "action-name": "Όνομα", + "action-name-required": "Απαιτείται όνομα ενέργειας.", + "action-name-not-unique": "Μια άλλη ενέργεια με το ίδιο όνομα υπάρχει ήδη.
Το όνομα ενέργειας πρέπει να είναι μοναδικό μέσα στην ίδια πηγή ενέργειας.", + "action-icon": "Εικονίδιο", + "action-type": "Τύπος", + "action-type-required": "Απαιτείται τύπος ενέργειας.", + "edit-action": "Επεξεργασία ενέργειας", + "delete-action": "Διαγραφή ενέργειας", + "delete-action-title": "Διαγραφή ενέργειας Widget", + "delete-action-text": "Είστε σίγουροι ότι θέλετε να διαγράψετε δράση widget με όνομα '{{actionName}}';" + }, + "widget-type": { + "import": "Εισαγωγή τύπου Widget", + "export": "Εξαγωγή τύπου Widget", + "export-failed-error": "Δεν είναι δυνατή η εξαγωγή τύπου Widget: {{error}}", + "create-new-widget-type": "Δημιουργία νέου τύπου Widget", + "widget-type-file": "Αρχείο τύπου Widget", + "invalid-widget-type-file-error": "Δεν είναι δυνατή η εισαγωγή τύπου Widget: Μη έγκυρη δομή δεδομένων τύπου Widget." + }, + "self-registration": { + "self-registration": "Αυτόματη εγγραφή", + "self-registration-url": "Αυτόματη εγγραφή URL", + "captcha-site-key": "reCAPTCHA κλειδί ιστότοπου", + "captcha-secret-key": "reCAPTCHA μυστικό κλειδί", + "notification-email": "Εmail γνωστοποίησης", + "privacy-policy-text": "Κείμενο πολιτικής απορρήτου", + "text-message-page": "Μήνυμα κειμένου για τη σελίδα εγγραφής" + }, + "white-labeling": { + "white-labeling": "Εμφάνιση", + "login-white-labeling": "Εμφάνιση Σύδεσης", + "preview": "Προεπισκόπηση", + "app-title": "Τίτλος εφαρμογής", + "favicon": "Εικονίδιο Ιστότοπου", + "favicon-description": "Εικόνα *.ico, *.gif or *.png με μέγιστο μέγεθος {{kbSize}} KBytes.", + "favicon-size-error": "Η εικόνα ιστότοπου είναι πολύ μεγάλη. Μέγιστο επιτρεπόμενο μέγεθος {{kbSize}} KBytes.", + "favicon-type-error": "Μη έγκυρη μορφή αρχείου εικόνας ιστότοπου. Μόνο εικόνες ICO, GIF ή PNG γίνονται αποδεκτές.", + "drop-favicon-image": "Σύρετε ένα εικονίδιο ιστότοπου ή κάντε κλικ για να επιλέξετε ένα αρχείο για μεταφόρτωση.", + "no-favicon-image": "Δεν έχει επιλεχθεί εικονίδιο", + "logo": "Logo", + "logo-description": "Οποιαδήποτε εικόνα με μέγιστο μέγεθος {{kbSize}} KBytes.", + "logo-size-error": "Η εικόνα του λογότυπου είναι πολύ μεγάλη. Μέγιστο επιτρεπόμενο μέγεθος {{kbSize}} KBytes.", + "logo-type-error": "Μη έγκυρη μορφή αρχείου λογότυπου. Μόνο εικόνες είναι αποδεκτές.", + "drop-logo-image": "Σείρετε μια εικόνα λογότυπου ή κάντε κλικ για να επιλέξετε ένα αρχείο για μεταφόρτωση.", + "no-logo-image": "Δεν έχει επιλεγεί λογότυπο", + "logo-height": "Ύψος λογότυπου, px", + "primary-palette": "Κύρια παλέτα", + "accent-palette": "Παλέτα τονισμών", + "customize-palette": "Προσαρμογή", + "edit-palette": "Επεξεργασία παλέτας", + "save-palette": "Αποθήκευση παλέτας", + "primary-background": "Κύριο χρώμα υποβάθρου", + "secondary-background": "Δευτερεύον χρώμα υποβάθρου", + "hue1": "HUE 1", + "hue2": "HUE 2", + "hue3": "HUE 3", + "page-background-color": "Χρώμα υποβάθρου σελίδας", + "dark-foreground": "Σκοτεινό χρώμα προσκηνίου", + "domain-name": "Όνομα Domain", + "help-link-base-url": "Base url για συνδέσμους βοηθείας", + "enable-help-links": "Ενεργοποίηση συνδέσμων βοηθείας", + "error-verification-url": "Ένα όνομα domain δεν πρέπει να περιέχει σύμβολα '/' και ':'. Παράδειγμα: gprs.cloud", + "show-platform-name-version": "Εμφάνιση ονόματος και έκδοσης πλατφόρμας", + "platform-name": "Όνομα πλατφόρμας", + "platform-version": "Έκδοση πλατφόρμας", + "version-mask": "{{name}} v.{{verion}}", + "position": { + "label": "Όνομα πλατφόρμας και θέση έκδοσης", + "under-logo": "Κάτω από το λογότυπο", + "bottom": "Στο κάτω μέρος της φόρμας σύνδεσης" + } + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Κυρ", + "Mon": "Δευ", + "Tue": "Τρι", + "Wed": "Τετ", + "Thu": "Πεμ", + "Fri": "Παρ", + "Sat": "Σαβ", + "Jan": "Ιαν", + "Feb": "Φεβ", + "Mar": "Μαρ", + "Apr": "Απρ", + "May": "Μάιος", + "Jun": "Ιουν", + "Jul": "Ιουλ", + "Aug": "Αυγ", + "Sep": "Σεπ", + "Oct": "Οκτ", + "Nov": "Νοε", + "Dec": "Δεκ", + "January": "Ιανουάριος", + "February": "Φεβρουάριος", + "March": "Μάρτιος", + "April": "Απρίλιος", + "June": "Ιούνιος", + "July": "Ιούλιος", + "August": "Αύγουστος", + "September": "Σεπτέμβριος", + "October": "Οκτώβριος", + "November": "Νοέμβριος", + "December": "Δεκέμβριος", + "Custom Date Range": "Προσαρμοσμένο εύρος ημερομηνιών", + "Date Range Template": "Πρότυπο εύρους ημερομηνιών", + "Today": "Σήμερα", + "Yesterday": "Χθες", + "This Week": "Αυτή την εβοδομάδα", + "Last Week": "Την προηγούμενη εβδομάδα", + "This Month": "Αυτόν τον μήνα", + "Last Month": "Τον προηγούμενο μήνα", + "Year": "Έτος", + "This Year": "Αυτό το χρόνο", + "Last Year": "Τον προηγούμενο χρόνο", + "Date picker": "Επιλογέας ημερομηνίας", + "Hour": "Ώρα", + "Day": "Ημέρα", + "Week": "Εβδομάδα", + "2 weeks": "2 Εβδομάδες", + "Month": "Μήνας", + "3 months": "3 Μήνες", + "6 months": "6 Μήνες", + "Custom interval": "Προσαρμοσμένο διάστημα", + "Interval": "Διάστημα", + "Step size": "Μέγεθος βήματος", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Εικονίδιο", + "select-icon": "Επιλογή εικονιδίου ", + "material-icons": "Υλικά εικονίδια", + "show-all": "Προβολή όλων των εικονιδίων" + }, + "subscription": { + "entity-limit-text": "Ωστόσο, μπορείτε να αναβαθμίσετε το πρόγραμμα εγγραφής σας για να αυξήσετε τα όριά σας.", + "upgrade-your-plan": "Αναβάθμιση του σχεδίου συνδρομής", + "white-labeling-feature": "Εμφάνιση χαρακτηριστικού", + "white-labeling-text-full": "Αλλάξτε το διακριτικό τίτλο της πλατφόρμας με το λογότυπο της εταιρείας ή του προϊόντος σας και το συνδυασμό χρωμάτων σε 2 λεπτά.

Αφαιρέστε το \"Powered By\" στο κάτω μέρος του dashboard.
Δεν απαιτείται κώδικας ή επανεκκίνηση υπηρεσίας. Επιτρέψτε και στους πελάτες σας να αλλάξουν το περιβάλλον τους.", + "enable-white-labeling": "Ενεργοποίηση εμφάνισης χαρακτηριστικού τώρα αναβαθμίζοντας το σχέδιο εγγραφής σας!", + "read-more": "Διαβάστε περισσότερα", + "white-labeling-video-text": "Δείτε το εκπαιδευτικό βίντεο παρακάτω για να δείτε πώς λειτουργεί αυτό το χαρακτηριστικό!" + }, + "subscription-error": { + "title": "Παραβίαση συνδρομής", + "warning-title": "Προειδοποίηση συνδρομής", + "limit-reached": { + "device-count": "Έχετε φτάσει τις μέγιστες συσκευές ({{value}}) που επιτρέπονται από το πρόγραμμα εγγραφής σας!", + "asset-count": "Έχετε φτάσει τα μέγιστα assets ({{value}}) που επιτρέπονται από το πρόγραμμα εγγραφής σας!" + }, + "feature-disabled": { + "white-labeling": "Η εμφάνιση χαρακτηριστικού δεν επιτρέπεται από το σχέδιο εγγραφής σας!" + } + }, + "custom": { + "widget-action": { + "action-cell-button": "Κουμπί ενέργειας κελιών", + "row-click": "Κλικ στη σειρά", + "polygon-click": "Κλικ στο πολύγωνο", + "marker-click": "Κλικ στο δείκτη", + "tooltip-tag-action": "Ετικέτα εργαλείου ενέργειας", + "node-selected": "Στον επιλεγμένο κόμβο", + "element-click": "Κλικ στο στοιχείο HTML" + } + }, + "language": { + "language": "Γλώσσα", + "locales": { + "de_DE": "Γερμανικά", + "fr_FR": "Γαλλικά", + "zh_CN": "Κινέζικα", + "en_US": "Αγγλικά", + "it_IT": "Ιταλικά", + "ko_KR": "Κορεάτικα", + "ru_RU": "Ρώσικα", + "es_ES": "Ισπανικά", + "ja_JA": "Ιαπωνικά", + "tr_TR": "Τούρκικα", + "fa_IR": "Περσικά", + "uk_UA": "Ουκρανικά", + "cs_CZ": "Τσέχικα", + "el_GR": "Ελληνικά" + } + } +} \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 26c704d127..3014d9741a 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1780,7 +1780,8 @@ "tr_TR": "Turkish", "fa_IR": "Persian", "uk_UA": "Ukrainian", - "cs_CZ": "Czech" + "cs_CZ": "Czech", + "el_GR": "Greek" } } } diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 81bf943f9e..d3d1c4042f 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -1757,7 +1757,8 @@ "tr_TR": "Turco", "fa_IR": "Persa", "uk_UA": "Ucraniano", - "cs_CZ": "Checo" + "cs_CZ": "Checo", + "el_GR": "Griego" } } } diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index 92dedd7df5..b25a532927 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -1167,7 +1167,8 @@ "tr_TR": "Turc", "fa_IR": "Persane", "uk_UA": "Ukrainien", - "cs_CZ": "Tchèque" + "cs_CZ": "Tchèque", + "el_GR": "Grec" } }, "layout": { diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 19f3a6263b..cf7529614b 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -1714,7 +1714,8 @@ "tr_TR": "Turco", "fa_IR": "Persiana", "uk_UA": "Ucraino", - "cs_CZ": "Ceco" + "cs_CZ": "Ceco", + "el_GR": "Greco" } } } diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index f95259c47c..5c343d84a5 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1776,7 +1776,8 @@ "ja_JA": "Японский", "fa_IR": "Персидский", "uk_UA": "Украинский", - "cs_CZ": "Чешский" + "cs_CZ": "Чешский", + "el_GR": "Греческий" } } } diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index 3f0b40157d..69d9047dd8 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -1604,7 +1604,8 @@ "tr_TR": "Türkçe", "fa_IR": "Farsça", "uk_UA": "Ukrayna", - "cs_CZ": "Çekçe" + "cs_CZ": "Çekçe", + "el_GR": "Yunanca" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index e5f89c11c3..6a07cb575c 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -2382,7 +2382,8 @@ "de_DE": "Німецька", "uk_UA": "Українська", "fa_IR": "Перська", - "cs_CZ": "Чеська" + "cs_CZ": "Чеська", + "el_GR": "Грецька" } } } \ No newline at end of file From f4656f3ea04a49574690bb07244497ec26e2e7c9 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Dec 2019 12:52:06 +0200 Subject: [PATCH 112/120] Add support for entityLabel in HTML value card, replace entityLabel on entityName, if label doesn't exist (#2239) --- application/src/main/data/json/system/widget_bundles/cards.json | 2 +- ui/src/app/common/utils.service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index bc06678759..d07186fd42 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -63,7 +63,7 @@ "resources": [], "templateHtml": "", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '
' + \n self.ctx.settings.cardHtml + \n '
';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n}\n\n", + "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '
' + \n self.ctx.settings.cardHtml + \n '
';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get('utils');\n var types = $injector.get('types');\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{' + types.translate.i18nPrefix + ':[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"
HTML code here
\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"
\\n
\\n
\\n

Value title

\\n
\\n ${My value:2} units.\\n
\\n
\\n Value description text\\n
\\n
\\n \\n
\\n
\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index 23a29308de..d7820f65c4 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -522,7 +522,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t } else if (variableName === 'deviceName') { label = label.split(variable).join(datasource.entityName); } else if (variableName === 'entityLabel') { - label = label.split(variable).join(datasource.entityLabel); + label = label.split(variable).join(datasource.entityLabel || datasource.entityName); } else if (variableName === 'aliasName') { label = label.split(variable).join(datasource.aliasName); } else if (variableName === 'entityDescription') { From 640e20197f7c19ad6a41205b677d9fdec8136200 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Dec 2019 15:21:41 +0200 Subject: [PATCH 113/120] added support batch telemetry --- .../src/main/resources/thingsboard.yml | 12 ++ ...sKvEntity.java => AbstractTsKvEntity.java} | 4 +- .../sqlts/timescale/TimescaleTsKvEntity.java | 4 +- .../server/dao/model/sqlts/ts/TsKvEntity.java | 4 +- .../dao/model/sqlts/ts/TsKvLatestEntity.java | 4 +- .../server/dao/sql/TbSqlBlockingQueue.java | 4 +- .../dao/sqlts/AbstractInsertRepository.java | 9 ++ .../sqlts/AbstractLatestInsertRepository.java | 4 + .../AbstractTimeseriesInsertRepository.java | 8 +- .../timescale/TimescaleInsertRepository.java | 120 +++++++++++++++++- .../timescale/TimescaleTimeseriesDao.java | 47 ++++++- .../sqlts/ts/HsqlLatestInsertRepository.java | 7 + .../ts/HsqlTimeseriesInsertRepository.java | 7 + .../server/dao/sqlts/ts/JpaTimeseriesDao.java | 73 +++++++++-- .../sqlts/ts/PsqlLatestInsertRepository.java | 119 +++++++++++++++++ .../ts/PsqlTimeseriesInsertRepository.java | 56 ++++++++ 16 files changed, 455 insertions(+), 27 deletions(-) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{AbsractTsKvEntity.java => AbstractTsKvEntity.java} (97%) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 16de3fd708..8be9a8a1c5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -204,6 +204,18 @@ sql: batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" + ts: + batch_size: "${SQL_TS_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" + ts_latest: + batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" + ts_timescale: + batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index d8c0e4ef0a..4c8a2606d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbsractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -35,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM @Data @MappedSuperclass -public abstract class AbsractTsKvEntity { +public abstract class AbstractTsKvEntity { protected static final String SUM = "SUM"; protected static final String AVG = "AVG"; @@ -80,7 +80,7 @@ public abstract class AbsractTsKvEntity { protected static boolean isAllNull(Object... args) { for (Object arg : args) { - if(arg != null) { + if (arg != null) { return false; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java index 3427c928f4..753e2c10fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java @@ -21,7 +21,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.ColumnResult; @@ -115,7 +115,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F resultSetMapping = "timescaleCountMapping" ) }) -public final class TimescaleTsKvEntity extends AbsractTsKvEntity implements ToData { +public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToData { @Id @Column(name = TENANT_ID_COLUMN) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index c5b9237f13..dab344cb44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.Entity; @@ -37,7 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Entity @Table(name = "ts_kv") @IdClass(TsKvCompositeKey.class) -public final class TsKvEntity extends AbsractTsKvEntity implements ToData { +public final class TsKvEntity extends AbstractTsKvEntity implements ToData { @Id @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java index 3c1f735834..fb558d7b87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvLatestEntity.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.Entity; @@ -37,7 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Entity @Table(name = "ts_kv_latest") @IdClass(TsKvLatestCompositeKey.class) -public final class TsKvLatestEntity extends AbsractTsKvEntity implements ToData { +public final class TsKvLatestEntity extends AbstractTsKvEntity implements ToData { @Id @Enumerated(EnumType.STRING) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java index 7630ccdaad..6e894fb382 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java @@ -92,8 +92,8 @@ public class TbSqlBlockingQueue implements TbSqlQueue { }); logExecutor.scheduleAtFixedRate(() -> { - log.info("Attributes queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", - queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); + log.info("[{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", + params.getLogName(), queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java index 919ab5314d..a4cd67abdc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -15,8 +15,11 @@ */ package org.thingsboard.server.dao.sqlts; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -57,6 +60,12 @@ public abstract class AbstractInsertRepository { @PersistenceContext protected EntityManager entityManager; + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + protected TransactionTemplate transactionTemplate; + protected static String getInsertOrUpdateStringHsql(String tableName, String constraint, String value, String nullValues) { return "MERGE INTO " + tableName + " USING(VALUES :entity_type, :entity_id, :key, :ts, :" + value + ") A (entity_type, entity_id, key, ts, " + value + ") ON " + constraint + " WHEN MATCHED THEN UPDATE SET " + tableName + "." + value + " = A." + value + ", " + tableName + ".ts = A.ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, " + value + ") VALUES (A.entity_type, A.entity_id, A.key, A.ts, A." + value + ")"; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java index a31b0e395b..e9b10eafa3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java @@ -19,11 +19,15 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import java.util.List; + @Repository public abstract class AbstractLatestInsertRepository extends AbstractInsertRepository { public abstract void saveOrUpdate(TsKvLatestEntity entity); + public abstract void saveOrUpdate(List entities); + protected void processSaveOrUpdate(TsKvLatestEntity entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { if (entity.getBooleanValue() != null) { saveOrUpdateBoolean(entity, requestBoolValue); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java index 6f1b9b1ed3..4787cd7a46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java @@ -17,13 +17,17 @@ package org.thingsboard.server.dao.sqlts; import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.model.sql.AbsractTsKvEntity; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; + +import java.util.List; @Repository -public abstract class AbstractTimeseriesInsertRepository extends AbstractInsertRepository { +public abstract class AbstractTimeseriesInsertRepository extends AbstractInsertRepository { public abstract void saveOrUpdate(T entity); + public abstract void saveOrUpdate(List entities); + protected void processSaveOrUpdate(T entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) { if (entity.getBooleanValue() != null) { saveOrUpdateBoolean(entity, requestBoolValue); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index 11f4ea4b5d..8493703275 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -15,13 +15,22 @@ */ package org.thingsboard.server.dao.sqlts.timescale; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + @TimescaleDBTsDao @PsqlDao @Repository @@ -30,14 +39,123 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, PSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, PSQL_ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String BATCH_UPDATE = + "UPDATE tenant_ts_kv SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_type = ? AND entity_id = ? and key = ? and ts = ?"; + + + private static final String INSERT_OR_UPDATE = + "INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TimescaleTsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(1, entities.get(i).getBooleanValue()); + } else { + ps.setNull(1, Types.BOOLEAN); + } + + ps.setString(2, replaceNullChars(entities.get(i).getStrValue())); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(3, entities.get(i).getLongValue()); + } else { + ps.setNull(3, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(4, entities.get(i).getDoubleValue()); + } else { + ps.setNull(4, Types.DOUBLE); + } + + ps.setString(5, entities.get(i).getTenantId()); + ps.setString(6, entities.get(i).getEntityId()); + ps.setString(7, entities.get(i).getKey()); + ps.setLong(8, entities.get(i).getTs()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + + int updatedCount = 0; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + updatedCount++; + } + } + + List insertEntities = new ArrayList<>(updatedCount); + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + insertEntities.add(entities.get(i)); + } + } + + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getTenantId()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }); + } + }); + } + @Override protected void saveOrUpdateBoolean(TimescaleTsKvEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 844f22a31c..961f545567 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -36,11 +37,16 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -66,6 +72,39 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Autowired private AbstractTimeseriesInsertRepository insertRepository; + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Value("${sql.ts_timescale.batch_size:1000}") + private int batchSize; + + @Value("${sql.ts_timescale.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.ts_timescale.stats_print_interval_ms:1000}") + private long statsPrintIntervalMs; + + private TbSqlBlockingQueue queue; + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + .logName("TS Timescale") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .build(); + queue = new TbSqlBlockingQueue<>(params); + queue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); + } + } + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -126,11 +165,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - log.trace("Saving entity to timescale db: {}", entity); - return insertService.submit(() -> { - insertRepository.saveOrUpdate(entity); - return null; - }); + return queue.add(entity); } @Override @@ -209,7 +244,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { List> result = new ArrayList<>(); timescaleTsKvEntities.forEach(entity -> { - if(entity != null && entity.isNotEmpty()) { + if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityIdStr); entity.setTenantId(tenantIdStr); entity.setKey(key); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java index 84250406d8..07650396f2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -22,6 +22,8 @@ import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.util.List; + @SqlTsDao @HsqlDao @Repository @@ -40,6 +42,11 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + + } + @Override protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java index 927bcd2443..8dbefd4443 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java @@ -22,6 +22,8 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.util.List; + @SqlTsDao @HsqlDao @Repository @@ -40,6 +42,11 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + + } + @Override protected void saveOrUpdateBoolean(TsKvEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java index b70b59604f..7c198d9a73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -38,6 +39,9 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; @@ -46,6 +50,8 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.SqlTsDao; import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -73,6 +79,63 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese @Autowired private AbstractLatestInsertRepository insertLatestRepository; + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Value("${sql.ts.batch_size:1000}") + private int tsBatchSize; + + @Value("${sql.ts.batch_max_delay:100}") + private long tsMaxDelay; + + @Value("${sql.ts.stats_print_interval_ms:1000}") + private long tsStatsPrintIntervalMs; + + @Value("${sql.ts_latest.batch_size:1000}") + private int tsLatestBatchSize; + + @Value("${sql.ts_latest.batch_max_delay:100}") + private long tsLatestMaxDelay; + + @Value("${sql.ts_latest.stats_print_interval_ms:1000}") + private long tsLatestStatsPrintIntervalMs; + + private TbSqlBlockingQueue tsQueue; + private TbSqlBlockingQueue tsLatestQueue; + + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() + .logName("TS") + .batchSize(tsBatchSize) + .maxDelay(tsMaxDelay) + .statsPrintIntervalMs(tsStatsPrintIntervalMs) + .build(); + tsQueue = new TbSqlBlockingQueue<>(tsParams); + tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); + + TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder() + .logName("TS Latest") + .batchSize(tsLatestBatchSize) + .maxDelay(tsLatestMaxDelay) + .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs) + .build(); + tsLatestQueue = new TbSqlBlockingQueue<>(tsLatestParams); + tsLatestQueue.init(logExecutor, v -> insertLatestRepository.saveOrUpdate(v)); + } + + @PreDestroy + private void destroy() { + if (tsQueue != null) { + tsQueue.destroy(); + } + + if (tsLatestQueue != null) { + tsLatestQueue.destroy(); + } + } + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -266,10 +329,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); log.trace("Saving entity: {}", entity); - return insertService.submit(() -> { - insertRepository.saveOrUpdate(entity); - return null; - }); + return tsQueue.add(entity); } @Override @@ -288,10 +348,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return insertService.submit(() -> { - insertLatestRepository.saveOrUpdate(latestEntity); - return null; - }); + return tsLatestQueue.add(latestEntity); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java index 5d50bf0dd9..92252e9d18 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java @@ -15,13 +15,22 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + @SqlTsDao @PsqlDao @Repository @@ -35,11 +44,121 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String BATCH_UPDATE = + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_type = ? AND entity_id = ? and key = ?"; + + + private static final String INSERT_OR_UPDATE = + "INSERT INTO ts_kv_latest (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_type, entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TsKvLatestEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); } + @Override + public void saveOrUpdate(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setLong(1, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(2, entities.get(i).getBooleanValue()); + } else { + ps.setNull(2, Types.BOOLEAN); + } + + ps.setString(3, replaceNullChars(entities.get(i).getStrValue())); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(4, entities.get(i).getLongValue()); + } else { + ps.setNull(4, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(5, entities.get(i).getDoubleValue()); + } else { + ps.setNull(5, Types.DOUBLE); + } + + ps.setString(6, entities.get(i).getEntityType().name()); + ps.setString(7, entities.get(i).getEntityId()); + ps.setString(8, entities.get(i).getKey()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + + int updatedCount = 0; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + updatedCount++; + } + } + + List insertEntities = new ArrayList<>(updatedCount); + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + insertEntities.add(entities.get(i)); + } + } + + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, insertEntities.get(i).getEntityType().name()); + ps.setString(2, insertEntities.get(i).getEntityId()); + ps.setString(3, insertEntities.get(i).getKey()); + ps.setLong(4, insertEntities.get(i).getTs()); + ps.setLong(9, insertEntities.get(i).getTs()); + + if (insertEntities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, insertEntities.get(i).getBooleanValue()); + ps.setBoolean(10, insertEntities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(10, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(11, replaceNullChars(entities.get(i).getStrValue())); + + + if (insertEntities.get(i).getLongValue() != null) { + ps.setLong(7, insertEntities.get(i).getLongValue()); + ps.setLong(12, insertEntities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(12, Types.BIGINT); + } + + if (insertEntities.get(i).getDoubleValue() != null) { + ps.setDouble(8, insertEntities.get(i).getDoubleValue()); + ps.setDouble(13, insertEntities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(13, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }); + } + }); + } + @Override protected void saveOrUpdateBoolean(TsKvLatestEntity entity, String query) { entityManager.createNativeQuery(query) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java index 0baea27d7b..edc37822b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; @@ -22,6 +23,11 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + @SqlTsDao @PsqlDao @Repository @@ -35,6 +41,10 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, PSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringPsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, PSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "INSERT INTO ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_type, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + @Override public void saveOrUpdate(TsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -83,4 +93,50 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo .setParameter("dbl_v", entity.getDoubleValue()) .executeUpdate(); } + + @Override + public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); + } + + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + } } \ No newline at end of file From 7f2c36952c22bf88e3082bece61c57f5eb2b646c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Dec 2019 16:40:45 +0200 Subject: [PATCH 114/120] added realization for Hsql --- .../sqlts/ts/HsqlLatestInsertRepository.java | 47 +++++++++++++++++ .../ts/HsqlTimeseriesInsertRepository.java | 50 ++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java index 07650396f2..12c9f9c0a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; @@ -22,6 +23,9 @@ import org.thingsboard.server.dao.sqlts.AbstractLatestInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.List; @SqlTsDao @@ -37,6 +41,16 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, LONG_V, HSQL_LATEST_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_LATEST_TABLE, TS_KV_LATEST_CONSTRAINT, DBL_V, HSQL_LATEST_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (ts_kv_latest.entity_type=T.entity_type " + + "AND ts_kv_latest.entity_id=T.entity_id " + + "AND ts_kv_latest.key=T.key) " + + "WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v " + + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; + @Override public void saveOrUpdate(TsKvLatestEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -44,7 +58,40 @@ public class HsqlLatestInsertRepository extends AbstractLatestInsertRepository { @Override public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + } + + ps.setString(6, entities.get(i).getStrValue()); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + } + } + @Override + public int getBatchSize() { + return entities.size(); + } + }); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java index 8dbefd4443..5d27b0d06f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.ts; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; @@ -22,6 +23,9 @@ import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.List; @SqlTsDao @@ -34,9 +38,20 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, BOOL_V, HSQL_ON_BOOL_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, STR_V, HSQL_ON_STR_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V , HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, LONG_V, HSQL_ON_LONG_VALUE_UPDATE_SET_NULLS); private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateStringHsql(TS_KV_TABLE, TS_KV_CONSTRAINT, DBL_V, HSQL_ON_DBL_VALUE_UPDATE_SET_NULLS); + private static final String INSERT_OR_UPDATE = + "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (ts_kv.entity_type=T.entity_type " + + "AND ts_kv.entity_id=T.entity_id " + + "AND ts_kv.key=T.key " + + "AND ts_kv.ts=T.ts) " + + "WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v " + + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_type, T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; + @Override public void saveOrUpdate(TsKvEntity entity) { processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); @@ -44,7 +59,40 @@ public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo @Override public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getEntityType().name()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + } + + ps.setString(6, entities.get(i).getStrValue()); + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + } + } + @Override + public int getBatchSize() { + return entities.size(); + } + }); } @Override From abed6e2a164bd1b20770b3dc8c48a4e81d0216bb Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 6 Dec 2019 11:45:38 +0200 Subject: [PATCH 115/120] refactored --- .../src/main/resources/thingsboard.yml | 6 +- .../timescale/TimescaleInsertRepository.java | 128 +++++------------- 2 files changed, 38 insertions(+), 96 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8be9a8a1c5..fcf62a659b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -213,9 +213,9 @@ sql: batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" ts_timescale: - batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" - batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" - stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" + batch_size: "${SQL_TS_TIMESCALE_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_TS_TIMESCALE_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_TS_TIMESCALE_BATCH_STATS_PRINT_MS:10000}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java index 8493703275..b47c79cb98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sqlts.timescale; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; import org.thingsboard.server.dao.util.PsqlDao; @@ -28,7 +26,6 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; -import java.util.ArrayList; import java.util.List; @TimescaleDBTsDao @@ -57,101 +54,46 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor @Override public void saveOrUpdate(List entities) { - transactionTemplate.execute(new TransactionCallbackWithoutResult() { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(1, entities.get(i).getBooleanValue()); - } else { - ps.setNull(1, Types.BOOLEAN); - } - - ps.setString(2, replaceNullChars(entities.get(i).getStrValue())); - - if (entities.get(i).getLongValue() != null) { - ps.setLong(3, entities.get(i).getLongValue()); - } else { - ps.setNull(3, Types.BIGINT); - } - - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(4, entities.get(i).getDoubleValue()); - } else { - ps.setNull(4, Types.DOUBLE); - } - - ps.setString(5, entities.get(i).getTenantId()); - ps.setString(6, entities.get(i).getEntityId()); - ps.setString(7, entities.get(i).getKey()); - ps.setLong(8, entities.get(i).getTs()); - } - - @Override - public int getBatchSize() { - return entities.size(); - } - }); - - int updatedCount = 0; - for (int i = 0; i < result.length; i++) { - if (result[i] == 0) { - updatedCount++; - } + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setString(1, entities.get(i).getTenantId()); + ps.setString(2, entities.get(i).getEntityId()); + ps.setString(3, entities.get(i).getKey()); + ps.setLong(4, entities.get(i).getTs()); + + if (entities.get(i).getBooleanValue() != null) { + ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(9, entities.get(i).getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); } - List insertEntities = new ArrayList<>(updatedCount); - for (int i = 0; i < result.length; i++) { - if (result[i] == 0) { - insertEntities.add(entities.get(i)); - } + ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); + + + if (entities.get(i).getLongValue() != null) { + ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(11, entities.get(i).getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + ps.setNull(11, Types.BIGINT); + } + + if (entities.get(i).getDoubleValue() != null) { + ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(12, entities.get(i).getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); } + } - jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - ps.setString(1, entities.get(i).getTenantId()); - ps.setString(2, entities.get(i).getEntityId()); - ps.setString(3, entities.get(i).getKey()); - ps.setLong(4, entities.get(i).getTs()); - - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(5, entities.get(i).getBooleanValue()); - ps.setBoolean(9, entities.get(i).getBooleanValue()); - } else { - ps.setNull(5, Types.BOOLEAN); - ps.setNull(9, Types.BOOLEAN); - } - - ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); - ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); - - - if (entities.get(i).getLongValue() != null) { - ps.setLong(7, entities.get(i).getLongValue()); - ps.setLong(11, entities.get(i).getLongValue()); - } else { - ps.setNull(7, Types.BIGINT); - ps.setNull(11, Types.BIGINT); - } - - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(8, entities.get(i).getDoubleValue()); - ps.setDouble(12, entities.get(i).getDoubleValue()); - } else { - ps.setNull(8, Types.DOUBLE); - ps.setNull(12, Types.DOUBLE); - } - } - - @Override - public int getBatchSize() { - return insertEntities.size(); - } - }); + @Override + public int getBatchSize() { + return entities.size(); } }); } From 93cc222ab996d23fdd4ee8a1a9e313a7a5e5f30a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Dec 2019 20:06:20 +0200 Subject: [PATCH 116/120] refactored URLs --- .../thingsboard/client/tools/RestClient.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 0af6d6d962..97ce718cc8 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -547,7 +547,7 @@ public class RestClient implements ClientHttpRequestInterceptor { addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( - baseURL + "/tenant/assets?type={type}&" + getUrlParams(pageLink), + baseURL + "/api/tenant/assets?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, @@ -1470,7 +1470,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult handleOneWayDeviceRPCRequest(String deviceId, String requestBody) { return restTemplate.exchange( - baseURL + "/oneway/{deviceId}", + baseURL + "/api/plugins/rpc/oneway/{deviceId}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference>() { @@ -1480,7 +1480,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult handleTwoWayDeviceRPCRequest(String deviceId, String requestBody) { return restTemplate.exchange( - baseURL + "/twoway/{deviceId}", + baseURL + "/api/plugins/rpc/twoway/{deviceId}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference>() { @@ -1579,7 +1579,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getAttributeKeys(String entityType, String entityId) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/keys/attributes", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1590,7 +1590,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getAttributeKeysByScope(String entityType, String entityId, String scope) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/keys/attributes/{scope}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes/{scope}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1602,7 +1602,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getAttributesResponseEntity(String entityType, String entityId, String keys) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/values/attributes?keys={keys}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1614,7 +1614,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getAttributesByScope(String entityType, String entityId, String scope, String keys) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1627,7 +1627,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getTimeseriesKeys(String entityType, String entityId) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/keys/timeseries", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/timeseries", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1638,7 +1638,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult getLatestTimeseries(String entityType, String entityId, String keys) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/values/timeseries?keys={keys}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1661,7 +1661,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("agg", agg == null ? "NONE" : agg); return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/values/timeseries?keys={keys}&startTs={startTs}&endTs={endTs}&interval={interval}&limit={limit}&agg={agg}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&startTs={startTs}&endTs={endTs}&interval={interval}&limit={limit}&agg={agg}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1671,7 +1671,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult saveDeviceAttributes(String deviceId, String scope, JsonNode request) { return restTemplate.exchange( - baseURL + "/{deviceId}/{scope}", + baseURL + "/api/plugins/telemetry/{deviceId}/{scope}", HttpMethod.POST, new HttpEntity<>(request), new ParameterizedTypeReference>() { @@ -1682,7 +1682,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult saveEntityAttributesV1(String entityType, String entityId, String scope, JsonNode request) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/{scope}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}", HttpMethod.POST, new HttpEntity<>(request), new ParameterizedTypeReference>() { @@ -1694,7 +1694,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult saveEntityAttributesV2(String entityType, String entityId, String scope, JsonNode request) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/attributes/{scope}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", HttpMethod.POST, new HttpEntity<>(request), new ParameterizedTypeReference>() { @@ -1706,7 +1706,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult saveEntityTelemetry(String entityType, String entityId, String scope, String requestBody) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/timeseries/{scope}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference>() { @@ -1718,7 +1718,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult saveEntityTelemetryWithTTL(String entityType, String entityId, String scope, Long ttl, String requestBody) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}/{ttl}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference>() { @@ -1746,7 +1746,7 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("rewriteLatestIfDeleted", String.valueOf(rewriteLatestIfDeleted)); return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", HttpMethod.DELETE, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1756,7 +1756,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult deleteEntityAttributes(String deviceId, String scope, String keys) { return restTemplate.exchange( - baseURL + "/{deviceId}/{scope}?keys={keys}", + baseURL + "/api/plugins/telemetry/{deviceId}/{scope}?keys={keys}", HttpMethod.DELETE, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1768,7 +1768,7 @@ public class RestClient implements ClientHttpRequestInterceptor { public DeferredResult deleteEntityAttributes(String entityType, String entityId, String scope, String keys) { return restTemplate.exchange( - baseURL + "/{entityType}/{entityId}/{scope}?keys={keys}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}?keys={keys}", HttpMethod.DELETE, HttpEntity.EMPTY, new ParameterizedTypeReference>() { From a6f7388c611ae071fbb1a8eda95ec7318a8c1b09 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 9 Dec 2019 13:47:53 +0200 Subject: [PATCH 117/120] added conditions for printing schedule logs --- .../org/thingsboard/server/actors/ActorSystemContext.java | 6 ++++-- .../org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index fa82d27971..f15a8bff63 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -298,8 +298,10 @@ public class ActorSystemContext { @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { if (statisticsEnabled) { - log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]", - jsInvokeRequestsCount.getAndSet(0), jsInvokeResponsesCount.getAndSet(0), jsInvokeFailuresCount.getAndSet(0)); + if (jsInvokeRequestsCount.get() > 0 || jsInvokeResponsesCount.get() > 0 || jsInvokeFailuresCount.get() > 0) { + log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]", + jsInvokeRequestsCount.getAndSet(0), jsInvokeResponsesCount.getAndSet(0), jsInvokeFailuresCount.getAndSet(0)); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java index 6e894fb382..da0a9cd70d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java @@ -92,8 +92,10 @@ public class TbSqlBlockingQueue implements TbSqlQueue { }); logExecutor.scheduleAtFixedRate(() -> { - log.info("[{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", - params.getLogName(), queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); + if (queue.size() > 0 || addedCount.get() > 0 || savedCount.get() > 0 || failedCount.get() > 0) { + log.info("[{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", + params.getLogName(), queue.size(), addedCount.getAndSet(0), savedCount.getAndSet(0), failedCount.getAndSet(0)); + } }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); } From fc95cabc7a93ac254224610cfab7b8a89859f8f0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 9 Dec 2019 15:22:29 +0200 Subject: [PATCH 118/120] fix nullPointer from method replaceNullChars --- .../thingsboard/server/dao/sqlts/AbstractInsertRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java index a4cd67abdc..e8134ca1a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java @@ -90,7 +90,7 @@ public abstract class AbstractInsertRepository { } protected String replaceNullChars(String strValue) { - if (removeNullChars) { + if (removeNullChars && strValue != null) { return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); } return strValue; From 752b3d90ab079c0916d78774eb9646699a1ba8af Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 10 Dec 2019 11:35:56 +0200 Subject: [PATCH 119/120] Removed redundant logging --- .../actors/service/DefaultActorService.java | 6 ++++- .../service/script/RemoteJsInvokeService.java | 7 ++++-- .../service/transport/RuleEngineStats.java | 15 +++++++----- .../nosql/CassandraBufferedRateExecutor.java | 24 ++++++++++++++----- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index 9ac8f86590..852fbe2884 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -201,7 +201,11 @@ public class DefaultActorService implements ActorService { @Scheduled(fixedDelayString = "${cluster.stats.print_interval_ms}") public void printStats() { if (statsEnabled) { - log.info("Cluster msgs sent [{}] received [{}]", sentClusterMsgs.getAndSet(0), receivedClusterMsgs.getAndSet(0)); + int sent = sentClusterMsgs.getAndSet(0); + int received = receivedClusterMsgs.getAndSet(0); + if (sent > 0 || received > 0) { + log.info("Cluster msgs sent [{}] received [{}]", sent, received); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 1fcab45c41..00c50940c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -87,12 +87,15 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { if (statsEnabled) { + int pushedMsgs = kafkaPushedMsgs.getAndSet(0); int invokeMsgs = kafkaInvokeMsgs.getAndSet(0); int evalMsgs = kafkaEvalMsgs.getAndSet(0); int failed = kafkaFailedMsgs.getAndSet(0); int timedOut = kafkaTimeoutMsgs.getAndSet(0); - log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", - kafkaPushedMsgs.getAndSet(0), invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { + log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java index 26a54a8548..13cae48383 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineStats.java @@ -70,11 +70,14 @@ public class RuleEngineStats { } public void printStats() { - log.info("Transport total [{}] sessionEvents [{}] telemetry [{}] attributes [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] " + - "toServerRpc [{}] subInfo [{}] claimDevice [{}] ", - totalCounter.getAndSet(0), sessionEventCounter.getAndSet(0), postTelemetryCounter.getAndSet(0), - postAttributesCounter.getAndSet(0), getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0), - subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0), - toServerRPCCallRequestCounter.getAndSet(0), subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0)); + int total = totalCounter.getAndSet(0); + if (total > 0) { + log.info("Transport total [{}] sessionEvents [{}] telemetry [{}] attributes [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] " + + "toServerRpc [{}] subInfo [{}] claimDevice [{}] ", + total, sessionEventCounter.getAndSet(0), postTelemetryCounter.getAndSet(0), + postAttributesCounter.getAndSet(0), getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0), + subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0), + toServerRPCCallRequestCounter.getAndSet(0), subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0)); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java index 37aaa532fd..7870135592 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java @@ -64,12 +64,24 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") public void printStats() { - log.info("Permits queueSize [{}] totalAdded [{}] totalLaunched [{}] totalReleased [{}] totalFailed [{}] totalExpired [{}] totalRejected [{}] " + - "totalRateLimited [{}] totalRateLimitedTenants [{}] currBuffer [{}] ", - getQueueSize(), - totalAdded.getAndSet(0), totalLaunched.getAndSet(0), totalReleased.getAndSet(0), - totalFailed.getAndSet(0), totalExpired.getAndSet(0), totalRejected.getAndSet(0), - totalRateLimited.getAndSet(0), rateLimitedTenants.size(), concurrencyLevel.get()); + int queueSize = getQueueSize(); + int totalAddedValue = totalAdded.getAndSet(0); + int totalLaunchedValue = totalLaunched.getAndSet(0); + int totalReleasedValue = totalReleased.getAndSet(0); + int totalFailedValue = totalFailed.getAndSet(0); + int totalExpiredValue = totalExpired.getAndSet(0); + int totalRejectedValue = totalRejected.getAndSet(0); + int totalRateLimitedValue = totalRateLimited.getAndSet(0); + int rateLimitedTenantsValue = rateLimitedTenants.size(); + int concurrencyLevelValue = concurrencyLevel.get(); + if (queueSize > 0 || totalAddedValue > 0 || totalLaunchedValue > 0 || totalReleasedValue > 0 || + totalFailedValue > 0 || totalExpiredValue > 0 || totalRejectedValue > 0 || totalRateLimitedValue > 0 || rateLimitedTenantsValue > 0 + || concurrencyLevelValue > 0) { + log.info("Permits queueSize [{}] totalAdded [{}] totalLaunched [{}] totalReleased [{}] totalFailed [{}] totalExpired [{}] totalRejected [{}] " + + "totalRateLimited [{}] totalRateLimitedTenants [{}] currBuffer [{}] ", + queueSize, totalAddedValue, totalLaunchedValue, totalReleasedValue, + totalFailedValue, totalExpiredValue, totalRejectedValue, totalRateLimitedValue, rateLimitedTenantsValue, concurrencyLevelValue); + } rateLimitedTenants.forEach(((tenantId, counter) -> { if (printTenantNames) { From 5f80dbe4b59eaa6c8c67812611867a5e6746fff8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Dec 2019 11:43:17 +0200 Subject: [PATCH 120/120] Update upgrade version --- msa/tb/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 6be069687f..f653fb7cab 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -40,7 +40,7 @@ tb-cassandra thingsboard /usr/share/${pkg.name} - 2.3.1 + 2.4.1